451 lines
13 KiB
Bash
Executable file
451 lines
13 KiB
Bash
Executable file
#!/bin/sh
|
|
# Copyright (c) 2017, 2021, 2023 Jonas 'Sortie' Termansen.
|
|
#
|
|
# Permission to use, copy, modify, and distribute this software for any
|
|
# purpose with or without fee is hereby granted, provided that the above
|
|
# copyright notice and this permission notice appear in all copies.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
#
|
|
# tix-fetch
|
|
# Download operating system files.
|
|
|
|
set -e
|
|
|
|
boot=false
|
|
collection=""
|
|
continue=""
|
|
execpatch=false
|
|
initrd=false
|
|
insecure_downgrade_to_http=false
|
|
insecure_no_check_certificate=false
|
|
input_release_file=
|
|
input_release_sig_file=
|
|
input_sha256sum=
|
|
normalize=false
|
|
output=""
|
|
outputdir=""
|
|
output_release_file= # TODO: A better term for this?
|
|
output_release_sig_file= # TODO: A better term for this?
|
|
output_sha256sum=
|
|
patch=false
|
|
package=false
|
|
release=false
|
|
repository_metadata=false
|
|
sha256=false
|
|
sha256sum=false
|
|
source=false
|
|
source_full=false
|
|
sysroot=""
|
|
toolchain=false
|
|
url=false
|
|
url_main=false
|
|
url_main_release=false
|
|
url_mirror=false
|
|
url_mirror_release=false
|
|
url_release_sig=false
|
|
url_sha256sum=false
|
|
upgrade=false
|
|
# TODO: Option to select this default:
|
|
# TODO: This hides errors. Fix wget so it has a quiet, but errors, mode.
|
|
wget_options="-q --show-progress"
|
|
|
|
# TODO: Ability to get source code easily for gcc/binutils/libstdc++.
|
|
|
|
dashdash=
|
|
previous_option=
|
|
for argument do
|
|
if test -n "$previous_option"; then
|
|
eval $previous_option=\$argument
|
|
previous_option=
|
|
shift
|
|
continue
|
|
fi
|
|
|
|
case $argument in
|
|
*=?*) parameter=$(expr "X$argument" : '[^=]*=\(.*\)' || true) ;;
|
|
*=) parameter= ;;
|
|
*) parameter=yes ;;
|
|
esac
|
|
|
|
case $dashdash$argument in
|
|
--) dashdash=yes ;;
|
|
-c) continue="-c" ;;
|
|
# TODO: Support -ofoo
|
|
-o) previous_option=output ;;
|
|
-O) previous_option=outputdir ;;
|
|
-q) wget_options="-q" ;;
|
|
-v) wget_options="-v" ;;
|
|
--boot) boot=true ;;
|
|
--collection=*) collection=$parameter ;;
|
|
--collection) previous_option=collection ;;
|
|
--continue) continue="--continue" ;;
|
|
--download-non-verbose) wget_options="-nv" ;;
|
|
--download-non-verbose) wget_options="-v" ;;
|
|
--download-quiet) wget_options="-q" ;;
|
|
--download-verbose) wget_options="-v" ;;
|
|
--execpatch) execpatch=true ;;
|
|
--initrd) initrd=true ;;
|
|
--input-release-file=*) input_release_file=$parameter ;;
|
|
--insecure-downgrade-to-http) insecure_downgrade_to_http=true ;;
|
|
--insecure-no-check-certificate) insecure_no_check_certificate=true ;;
|
|
--input-release-file) previous_option=input_release_file ;;
|
|
--input-release-sig-file=*) input_release_sig_file=$parameter ;;
|
|
--input-release-sig-file) previous_option=input_release_sig_file ;;
|
|
--input-sha256sum=*) input_sha256sum=$parameter ;;
|
|
--input-sha256sum) previous_option=input_sha256sum ;;
|
|
--normalize) normalize=true ;;
|
|
--nv) wget_options="-nv" ;;
|
|
--outputdir=*) outputdir=$parameter ;;
|
|
--outputdir) previous_option=outputdir ;;
|
|
--output=*) output=$parameter ;;
|
|
--output) previous_option=output ;;
|
|
--output-release-file=*) output_release_file=$parameter ;;
|
|
--output-release-file) previous_option=output_release_file ;;
|
|
--output-release-sig-file=*) output_release_sig_file=$parameter ;;
|
|
--output-release-sig-file) previous_option=output_release_sig_file ;;
|
|
--output-sha256sum=*) output_sha256sum=$parameter ;;
|
|
--output-sha256sum) previous_option=output_sha256sum ;;
|
|
--output-upgrade-file=*) output_upgrade_file=$parameter ;;
|
|
--output-upgrade-file) previous_option=output_upgrade_file ;;
|
|
--package) package=true ;;
|
|
--patch) patch=true ;;
|
|
--repository-metadata) repository_metadata=true ;;
|
|
--sha256) sha256=true ;;
|
|
--sha256sum) sha256sum=true ;;
|
|
--source-full) source_full=true ;;
|
|
--source) source=true ;;
|
|
--sysroot) previous_option=sysroot ;;
|
|
--sysroot=*) sysroot=$parameter ;;
|
|
--toolchain) toolchain=true ;;
|
|
--upgrade) upgrade=true ;;
|
|
--url) url=true ;;
|
|
--url-main) url_main=true ;;
|
|
--url-mirror) url_mirror=true ;;
|
|
--url-main-release) url_main_release=true ;;
|
|
--url-mirror-release) url_mirror_release=true ;;
|
|
--url-release-sig) url_release_sig=true ;;
|
|
--url-sha256sum) url_sha256sum=true ;;
|
|
--wget-options) previous_option=wget_options ;;
|
|
--wget-options=*) wget_options=$parameter ;;
|
|
-*) echo "$0: unrecognized option $argument" >&2
|
|
exit 1 ;;
|
|
*) break ;;
|
|
esac
|
|
|
|
shift
|
|
done
|
|
|
|
if test -n "$previous_option"; then
|
|
echo "$0: option '$argument' requires an argument" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# TODO: Mutually incompatible options.
|
|
|
|
conf() {
|
|
sed -E -e 's/([a-zA-Z0-9_]+) *? *= */\U\1=/' \
|
|
-e 's/=yes$/=true/' -e 's/no$/=false/' "$3" | \
|
|
tix-vars -d "$2" - "$4"
|
|
}
|
|
|
|
tmpdir=$(mktemp -dt tix-fetch.XXXXXX)
|
|
trap 'rm -rf -- "$tmpdir"' EXIT HUP INT QUIT TERM
|
|
|
|
upgrade_conf="${collection%/}/etc/upgrade.conf"
|
|
CHANNEL=$(conf -d '' "$upgrade_conf" CHANNEL)
|
|
RELEASE_KEY=$(conf -d '' "$upgrade_conf" RELEASE_KEY)
|
|
RELEASE_SIG_URL=$(conf -d '' "$upgrade_conf" RELEASE_SIG_URL)
|
|
PREFERRED_MIRROR=$(conf -d '' "$upgrade_conf" PREFERRED_MIRROR)
|
|
FORCE_MIRROR=$(conf -d '' "$upgrade_conf" FORCE_MIRROR)
|
|
USER_AGENT="$(uname -s)/$(uname -r) ($(uname -m); $(uname -v))"
|
|
|
|
if $insecure_no_check_certificate; then
|
|
echo "$0: warning: insecurely not checking https certificates" >&2
|
|
wget_options="$wget_options --no-check-certificate"
|
|
fi
|
|
|
|
if $insecure_downgrade_to_http; then
|
|
echo "$0: warning: insecurely downloading without https" >&2
|
|
RELEASE_SIG_URL="$(echo "$RELEASE_SIG_URL" | sed -E 's,^https:,http:,')"
|
|
fi
|
|
|
|
if $url_release_sig; then
|
|
printf "%s\n" "$RELEASE_SIG_URL"
|
|
exit
|
|
fi
|
|
|
|
# HACK: Provide more useful errors when wget is silent:
|
|
do_wget() {
|
|
(set +e
|
|
wget "$@"
|
|
status=$?
|
|
set -e
|
|
what=
|
|
case $status in
|
|
0) exit 0 ;;
|
|
1) what="Generic error" ;;
|
|
2) what="Parse error" ;;
|
|
3) what="File I/O error" ;;
|
|
4) what="Network I/O error" ;;
|
|
5) what="Transport Layer Security verification failure" ;;
|
|
6) what="Username/password failure" ;;
|
|
7) what="Protocol error" ;;
|
|
8) what="Error response" ;;
|
|
*) what="Exit code $status" ;;
|
|
esac
|
|
echo "$0: $what when running: wget $@" >&2
|
|
exit $status)
|
|
}
|
|
|
|
# Fetch signed release description.
|
|
download_release_sh() {
|
|
(cd "$tmpdir" &&
|
|
do_wget -U "$USER_AGENT" $wget_options -O release.sh.sig \
|
|
-- "$RELEASE_SIG_URL")
|
|
signify -Vq -p "$RELEASE_KEY" -em "$tmpdir/release.sh"
|
|
}
|
|
|
|
true > "$tmpdir/upgrade.sh"
|
|
|
|
if [ -z "$input_release_file" -a -z "$input_release_sig_file" ]; then
|
|
download_release_sh
|
|
# TODO: tix-vars's output is not quoted so it can be input again.
|
|
tix-vars "$tmpdir/release.sh" | \
|
|
grep -E '^UPGRADE_=' | \
|
|
cat > "$tmpdir/upgrade.sh"
|
|
UPGRADE_SIG_URL=$(tix-vars -d '' "$tmpdir/upgrade.sh" UPGRADE_SIG_URL)
|
|
if $upgrade && [ -n "$UPGRADE_SIG_URL" ]; then
|
|
RELEASE_SIG_URL="$UPGRADE_SIG_URL"
|
|
RELEASE_KEY=$(tix-vars "$tmpdir/upgrade.sh" UPGRADE_KEY)
|
|
download_release_sh
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$input_release_file" ]; then
|
|
cp -T -- "$input_release_file" "$tmpdir/release.sh"
|
|
elif [ -n "$input_release_sig_file" ]; then
|
|
signify -Vq -p "$RELEASE_KEY" -em "$tmpdir/release.sh"
|
|
fi
|
|
|
|
# Store the signed release file if requested.
|
|
if [ -n "$output_release_sig_file" ]; then
|
|
cp -T -- "$tmpdir/release.sh.sig" "$output_release_sig_file"
|
|
fi
|
|
|
|
# Store the release file (without signature) if requested.
|
|
if [ -n "$output_release_file" ]; then
|
|
cp -T -- "$tmpdir/release.sh" "$output_release_file"
|
|
fi
|
|
|
|
# Store the upgrade file if requested.
|
|
if [ -n "$output_upgrade_file" ]; then
|
|
cp -T -- "$tmpdir/upgrade.sh" "$output_upgrade_file"
|
|
fi
|
|
|
|
# Load the release description.
|
|
# TODO: SECURITY: Protect against responding with older release.sh.
|
|
|
|
# TODO: DO NOT SUBMIT: Temporary compatibility.
|
|
MAIN=$(tix-vars -d '' "$tmpdir/release.sh" MAIN)
|
|
MASTER=$(tix-vars -d '' "$tmpdir/release.sh" MASTER)
|
|
if [ -z "$MAIN" ]; then
|
|
MAIN="$MASTER"
|
|
fi
|
|
|
|
RELEASE=$(tix-vars "$tmpdir/release.sh" RELEASE)
|
|
MACHINE=$(tix-vars "$tmpdir/release.sh" MACHINE)
|
|
SHA256SUM_FILE=$(tix-vars -d sha256sum "$tmpdir/release.sh" SHA256SUM_FILE)
|
|
SHA256SUM_SHA256SUM=$(tix-vars "$tmpdir/release.sh" SHA256SUM_SHA256SUM)
|
|
MIRRORS=$(tix-vars -d '' "$tmpdir/release.sh" MIRRORS)
|
|
|
|
if $url_main; then
|
|
printf "%s\n" "$MAIN"
|
|
exit
|
|
elif $url_main_release; then
|
|
printf "%s\n" "$MAIN/$RELEASE"
|
|
exit
|
|
fi
|
|
|
|
# Default to the main mirror but switch to the preferred mirror if the release
|
|
# description knows about the mirror and believes it to be trustworthy.
|
|
MIRROR="$MAIN"
|
|
for POTENTIAL_MIRROR in $MIRRORS; do
|
|
if [ "$POTENTIAL_MIRROR" = "$PREFERRED_MIRROR" ]; then
|
|
MIRROR="$PREFERRED_MIRROR"
|
|
fi
|
|
done
|
|
if [ -n "$PREFERRED_MIRROR" ] && [ "$MIRROR" != "$PREFERRED_MIRROR" ]; then
|
|
if [ "$FORCE_MIRROR" = true ]; then
|
|
MIRROR="$PREFERRED_MIRROR"
|
|
else
|
|
echo "$0: warning: ignoring unsupported mirror $PREFERRED_MIRROR" >&2
|
|
fi
|
|
fi
|
|
|
|
# TODO: Make sure the distant future http downgrade is supported.
|
|
if $insecure_downgrade_to_http; then
|
|
MIRROR="$(echo "$MIRROR" | sed -E 's,^https:,http:,')"
|
|
fi
|
|
|
|
if $url_mirror; then
|
|
printf "%s\n" "$MIRROR"
|
|
exit
|
|
elif $url_mirror_release; then
|
|
printf "%s\n" "$MIRROR/$RELEASE"
|
|
exit
|
|
fi
|
|
|
|
RELEASE_URL="$MIRROR/$RELEASE"
|
|
|
|
# Fetch sha256sum file and check its SHA256 hash with the release description.
|
|
if $url_sha256sum; then
|
|
printf "%s\n" "$RELEASE_URL/$SHA256SUM_FILE"
|
|
exit
|
|
fi
|
|
if [ -z "$input_sha256sum" ]; then
|
|
# TODO: If the mirror doesn't work, try the main.
|
|
(cd "$tmpdir" &&
|
|
do_wget -U "$USER_AGENT" $wget_options -O sha256sum \
|
|
-- "$RELEASE_URL/$SHA256SUM_FILE")
|
|
else
|
|
cp -T -- "$input_sha256sum" "$tmpdir/sha256sum"
|
|
fi
|
|
# TODO: Check if upstream release description changed, if so, start over.
|
|
echo "$SHA256SUM_SHA256SUM $tmpdir/sha256sum" | sha256sum -cq
|
|
|
|
# Store the sha256sum file if requested.
|
|
if [ -n "$output_sha256sum" ]; then
|
|
cp -T -- "$tmpdir/sha256sum" "$output_sha256sum"
|
|
fi
|
|
|
|
escape_extended_regex() {
|
|
printf "%s\n" "$1" | sed -E -e 's/[[$()*?\+.^{|}]/\\\0/g'
|
|
}
|
|
|
|
request() {
|
|
REQUEST="$1"
|
|
REQUESTDIR="$2"
|
|
REQUESTFINAL="${3-$1}"
|
|
FULLREQUEST="$REQUESTDIR$REQUEST"
|
|
|
|
if $url; then
|
|
printf '%s\n' "$RELEASE_URL/$FULLREQUEST"
|
|
return
|
|
fi
|
|
|
|
if $sha256 || $sha256sum; then
|
|
set +e # Don't fail if grep exits 1 (no match).
|
|
# TODO: Should this be a checksum(1) feature to look up a hash?
|
|
grep -E "^[0-9a-fA-F]{64} $(escape_extended_regex "$FULLREQUEST")$" \
|
|
"$tmpdir/sha256sum" > "$tmpdir/match"
|
|
EXITCODE=$?
|
|
set -e
|
|
if [ 2 -le "$EXITCODE" ]; then (exit $EXITCODE); fi
|
|
if $sha256 && [ -s "$tmpdir/match" ]; then
|
|
grep -Eo '^[0-9a-fA-F]{64}' "$tmpdir/match"
|
|
fi
|
|
if $sha256sum && [ -s "$tmpdir/match" ]; then
|
|
cat "$tmpdir/match"
|
|
fi
|
|
return
|
|
fi
|
|
|
|
# Decide the final location the file will end up.
|
|
if [ -n "$output" ]; then
|
|
FINAL="$output"
|
|
OUTPUTDIR=$(dirname -- "$output")
|
|
elif [ -n "$outputdir" ]; then
|
|
FINAL="$outputdir/$REQUESTFINAL"
|
|
OUTPUTDIR="$outputdir"
|
|
else
|
|
FINAL="$REQUESTFINAL"
|
|
OUTPUTDIR=.
|
|
fi
|
|
|
|
# If a resumable download, store the file directly to the destination path.
|
|
# Otherwise download to a temporary directory and move only to the final
|
|
# location if the cryptographic check is passed.
|
|
if [ -n "$continue" ]; then
|
|
DOWNLOADDIR="$OUTPUTDIR"
|
|
OUTPUT="$FINAL"
|
|
else
|
|
DOWNLOADDIR="$tmpdir/download"
|
|
OUTPUT="$DOWNLOADDIR/$REQUEST"
|
|
mkdir -p -- "$DOWNLOADDIR"
|
|
fi
|
|
|
|
# Fetch the file.
|
|
# TODO: If the mirror doesn't work, try the main.
|
|
(cd "$DOWNLOADDIR" &&
|
|
mkdir -p -- "$(dirname -- "$REQUEST")" &&
|
|
do_wget -U "$USER_AGENT" $wget_options $continue -O "$REQUEST" \
|
|
-- "$RELEASE_URL/$FULLREQUEST")
|
|
|
|
# Verify the cryptographic integrity of the fetched file.
|
|
ABSOLUTE_OUTPUT=$(realpath -- "$OUTPUT")
|
|
mkdir -p -- "$tmpdir/check"
|
|
(cd "$tmpdir/check" &&
|
|
mkdir -p -- "$(dirname -- "$FULLREQUEST")"
|
|
ln -s -- "$ABSOLUTE_OUTPUT" "$FULLREQUEST"
|
|
if ! sha256sum -q -C "$tmpdir/sha256sum" -- "$FULLREQUEST"; then
|
|
# Don't leave behind a file that didn't pass a cryptographic check.
|
|
if [ -n "$continue" ]; then
|
|
# TODO: Check if upstream release description changed, if so, start over.
|
|
echo "error: Deleting corrupted output file: $OUTPUT" 2>&1
|
|
rm -f -- "$OUTPUT"
|
|
fi
|
|
exit 1
|
|
fi)
|
|
rm -rf -- "$tmpdir/check"
|
|
|
|
# Move the file to the final destination if not already.
|
|
if [ -z "$continue" ]; then
|
|
if [ -z "$output" ]; then
|
|
(cd "$OUTPUTDIR" && mkdir -p -- "$(dirname -- "$REQUESTFINAL")")
|
|
fi
|
|
cp -T -- "$OUTPUT" "$FINAL"
|
|
rm -rf -- "$tmpdir/download"
|
|
fi
|
|
}
|
|
|
|
if $release; then
|
|
MIRRORS=$(tix-vars "$tmpdir/release.sh" BUILD_FILE)
|
|
request "$BUILD_FILE" "" "$(basename -- "$BUILD_FILE")"
|
|
fi
|
|
|
|
# TODO: --source, --source-full
|
|
# TODO: --binutils, --gcc, --libstdc++
|
|
|
|
# Fetch each of the specified signed files from the mirror.
|
|
for REQUEST; do
|
|
if $package; then
|
|
REQUEST="$REQUEST.tix.tar.xz"
|
|
REQUESTDIR="repository/$MACHINE-sortix/"
|
|
elif $repository_metadata; then
|
|
REQUESTDIR="repository/$MACHINE-sortix/"
|
|
elif $boot; then
|
|
REQUEST="$REQUEST"
|
|
REQUESTDIR="$MACHINE/boot/"
|
|
elif $initrd; then
|
|
REQUEST="$REQUEST.tar.xz"
|
|
REQUESTDIR="$MACHINE/boot/"
|
|
elif $patch; then
|
|
REQUEST="$REQUEST.patch"
|
|
REQUESTDIR="patches/"
|
|
elif $normalize; then
|
|
REQUEST="$REQUEST.normalize"
|
|
REQUESTDIR="patches/"
|
|
elif $toolchain; then
|
|
REQUESTDIR="toolchain/"
|
|
else
|
|
REQUESTDIR=""
|
|
fi
|
|
request "$REQUEST" "$REQUESTDIR"
|
|
done
|