joeylib2/installer/lib/bundle.sh

170 lines
6 KiB
Bash

# shellcheck shell=bash
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
# See LICENSE.md for license information.
# SPDX-License-Identifier: MIT
#
# Private files: the ones in files.sha1, which are never fetched from a
# public source. They arrive from the owner's bundle (JOEY_BUNDLE_URL) or
# from `install.sh import`. Sourced after lib/common.sh.
JOEY_FILES_TABLE="$JOEY_INSTALLER/files.sha1"
JOEY_BUNDLE_CONF="$JOEY_INSTALLER/bundle.conf"
# privateTable -- print "sha1 destination" for every private file.
privateTable() {
awk '!/^#/ && NF >= 2 { print $1, $2 }' "$JOEY_FILES_TABLE"
}
# privateSha1For DEST -- the checksum files.sha1 expects at DEST.
privateSha1For() {
awk -v d="$1" '!/^#/ && $2 == d { print $1 }' "$JOEY_FILES_TABLE"
}
# privatePresent DEST -- true when DEST holds the expected file.
privatePresent() {
local want
want="$(privateSha1For "$1")"
[ -n "$want" ] || die "no files.sha1 entry for $1"
[ -f "$JOEY_REPO/$1" ] && [ "$(sha1sum "$JOEY_REPO/$1" | cut -d' ' -f1)" = "$want" ]
}
# importOneFile FILE -- install FILE if its checksum is a private file's.
# Prints the destination it was installed to.
importOneFile() {
local sum
local dest
sum="$(sha1sum "$1" | cut -d' ' -f1)"
dest="$(privateTable | awk -v s="$sum" '$1 == s { print $2 }')"
[ -n "$dest" ] || return 0
# Importing a file that is already in place is a no-op, not an error.
if [ "$(realpath "$1")" != "$(realpath -m "$JOEY_REPO/$dest")" ]; then
install -D -m 644 "$1" "$JOEY_REPO/$dest"
fi
printf '%s\n' "$dest"
}
# importPaths PATH... -- scan files, directories, .zip and .7z archives and
# Amiga .adf disk images and install every private file found. Prints what
# was installed. (Workbench's own disk is how most people own AmigaOS files
# such as Devs/serial.device; a .7z is opened with 7z, when installed.)
importPaths() {
local path
local file
local tmp
tmp="$(mktemp -d)"
for path in "$@"; do
[ -e "$path" ] || die "no such file or directory: $path"
while IFS= read -r -d '' file; do
case "$file" in
*.zip|*.ZIP)
rm -rf "$tmp/zip"
mkdir -p "$tmp/zip"
if unzip -qq -o "$file" -d "$tmp/zip" 2>/dev/null; then
find "$tmp/zip" -type f -print0 | while IFS= read -r -d '' inner; do
importOneFile "$inner"
done
fi
;;
*.7z|*.7Z)
if ! command -v 7z > /dev/null 2>&1; then
warn "skipping $file: opening a .7z needs 7z (Debian/Ubuntu: p7zip-full, Fedora: p7zip-plugins)"
continue
fi
rm -rf "$tmp/7z"
mkdir -p "$tmp/7z"
if 7z x -y -o"$tmp/7z" "$file" > /dev/null 2>&1; then
find "$tmp/7z" -type f -print0 | while IFS= read -r -d '' inner; do
importOneFile "$inner"
done
fi
rm -rf "$tmp/7z"
;;
*.adf|*.ADF)
rm -rf "$tmp/adf"
mkdir -p "$tmp/adf"
if python3 "$JOEY_REPO/tools/adfTool.py" extractall "$file" "$tmp/adf" 2>/dev/null; then
find "$tmp/adf" -type f -print0 | while IFS= read -r -d '' inner; do
importOneFile "$inner"
done
fi
;;
*)
importOneFile "$file"
;;
esac
done < <(find "$path" -type f -print0)
done
rm -rf "$tmp"
}
# bundleUrl -- the configured bundle URL, or nothing.
bundleUrl() {
if [ -n "${JOEY_BUNDLE_URL:-}" ]; then
printf '%s\n' "$JOEY_BUNDLE_URL"
elif [ -f "$JOEY_BUNDLE_CONF" ]; then
(. "$JOEY_BUNDLE_CONF" && printf '%s\n' "${JOEY_BUNDLE_URL:-}")
fi
}
# bundleFetch -- download the owner's bundle (if one is configured) and
# import what it contains. Its checksum is not pinned -- the owner updates
# it -- but every file taken from it is checked against files.sha1.
bundleFetch() {
local url
local tmp
url="$(bundleUrl)"
[ -n "$url" ] || return 1
tmp="$(mktemp -d)"
info "fetching the private bundle"
curl -fL --retry 3 --connect-timeout 30 --progress-bar -o "$tmp/bundle.tar.gz" "$url" || { rm -rf "$tmp"; die "could not download the private bundle from $url"; }
mkdir -p "$tmp/x"
tar -xzf "$tmp/bundle.tar.gz" -C "$tmp/x" || { rm -rf "$tmp"; die "the private bundle is not a .tar.gz"; }
importPaths "$tmp/x" >/dev/null
rm -rf "$tmp"
}
# privateEnsure DEST -- make DEST present, from the bundle if configured,
# or stop and explain how to supply it.
privateEnsure() {
local what
privatePresent "$1" && return 0
bundleFetch || true
privatePresent "$1" && return 0
what="$(awk -v d="$1" '!/^#/ && $2 == d { $1 = ""; $2 = ""; sub(/^ +/, ""); print }' "$JOEY_FILES_TABLE")"
die "$1 is missing: $what (sha1 $(privateSha1For "$1")).
This file is not downloaded from public sources. Supply your copy with
./install.sh import PATH (files, folders or .zip archives)
or set JOEY_BUNDLE_URL in installer/bundle.conf to your private bundle."
}
# bundleCreate OUTFILE -- pack every private file present into OUTFILE
# (a .tar.gz) for the owner to host.
bundleCreate() {
local out="$1"
local dest
local sum
local tmp
local count=0
tmp="$(mktemp -d)"
while read -r sum dest; do
if privatePresent "$dest"; then
install -D -m 644 "$JOEY_REPO/$dest" "$tmp/$dest"
count=$((count + 1))
else
warn "not bundled (not present here): $dest"
fi
done < <(privateTable)
[ "$count" -gt 0 ] || { rm -rf "$tmp"; die "none of the private files are present to bundle"; }
tar -czf "$out" -C "$tmp" .
rm -rf "$tmp"
ok "bundled $count private files into $out"
}