From 9a3e5ee33a0d5adddce7132c649e670a5bab346f Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sun, 2 Apr 2017 18:41:59 +0200 Subject: [PATCH] Add tix-upgrade(8). --- Makefile | 6 + build-aux/ports.conf | 4 +- build-aux/version.mak | 4 + etc/signify/sortix-1.1.pub | 2 + etc/signify/sortix-1.1dev.pub | 2 + etc/signify/sortix-1.2.pub | 2 + etc/signify/sortix-1.2dev.pub | 2 + share/man/man5/upgrade.conf.5 | 91 +++++++- sysinstall/conf.c | 37 ++- sysinstall/conf.h | 5 + sysinstall/sysinstall.c | 15 ++ sysinstall/sysmerge.c | 4 + sysinstall/sysupgrade.c | 1 + tix/Makefile | 3 + tix/tix-clean | 68 ++++++ tix/tix-fetch | 424 ++++++++++++++++++++++++++++++++++ tix/tix-upgrade | 230 ++++++++++++++++++ tix/tix.c | 45 +++- 18 files changed, 932 insertions(+), 13 deletions(-) create mode 100644 etc/signify/sortix-1.1.pub create mode 100644 etc/signify/sortix-1.1dev.pub create mode 100644 etc/signify/sortix-1.2.pub create mode 100644 etc/signify/sortix-1.2dev.pub create mode 100755 tix/tix-clean create mode 100755 tix/tix-fetch create mode 100755 tix/tix-upgrade diff --git a/Makefile b/Makefile index 25d509ec..56d2862a 100644 --- a/Makefile +++ b/Makefile @@ -237,6 +237,8 @@ sysroot-system: sysroot-fsh sysroot-base-headers echo /etc/sortix-release >> "$(SYSROOT)/tix/manifest/system" ln -sf sortix-release "$(SYSROOT)/etc/os-release" echo /etc/os-release >> "$(SYSROOT)/tix/manifest/system" + find etc | sed -e 's,^,/,' >> "$(SYSROOT)/tix/manifest/system" + cp -RT etc "$(SYSROOT)/etc" find share | sed -e 's,^,/,' >> "$(SYSROOT)/tix/manifest/system" cp -RT share "$(SYSROOT)/share" export SYSROOT="$(SYSROOT)" && \ @@ -274,6 +276,7 @@ else ifneq ($(SORTIX_INCLUDE_SOURCE),no) cp Makefile -t "$(SYSROOT)/src" cp README -t "$(SYSROOT)/src" cp -RT build-aux "$(SYSROOT)/src/build-aux" + cp -RT etc "$(SYSROOT)/src/etc" cp -RT share "$(SYSROOT)/src/share" (for D in $(MODULES); do (cp -R $$D -t "$(SYSROOT)/src" && $(MAKE) -C "$(SYSROOT)/src/$$D" clean) || exit $$?; done) endif @@ -457,6 +460,9 @@ $(LIVE_INITRD): sysroot echo "include /etc/default/passwd.d/*" >> $(LIVE_INITRD).d/etc/passwd echo "root::0:root" > $(LIVE_INITRD).d/etc/group echo "include /etc/default/group.d/*" >> $(LIVE_INITRD).d/etc/group + (echo 'channel = $(CHANNEL)' && \ + echo 'release_key = $(RELEASE_KEY)' && \ + echo 'release_sig_url = $(RELEASE_SIG_URL)') > $(LIVE_INITRD).d/etc/upgrade.conf mkdir -p $(LIVE_INITRD).d/home mkdir -p $(LIVE_INITRD).d/root -m 700 cp -RT "$(SYSROOT)/etc/skel" $(LIVE_INITRD).d/root diff --git a/build-aux/ports.conf b/build-aux/ports.conf index 10aa6179..b1550856 100644 --- a/build-aux/ports.conf +++ b/build-aux/ports.conf @@ -1,3 +1,3 @@ -set_minimal="cut dash e2fsprogs grep grub mdocml sed xargs" -set_basic="$set_minimal binutils bison bzip2 diffutils ed flex gawk gcc git gzip libcurl libcurses libssl libstdc++ nano ntpd make patch pkg-config python ssh tar vim wget xz xorriso" +set_minimal="cut dash e2fsprogs grep grub libssl mdocml sed signify tar wget xargs xorriso xz" +set_basic="$set_minimal binutils bison bzip2 diffutils ed flex gawk gcc git gzip libcurl libcurses libstdc++ nano ntpd make patch pkg-config python ssh vim" sets="basic minimal" diff --git a/build-aux/version.mak b/build-aux/version.mak index 880070d0..b5ca00e9 100644 --- a/build-aux/version.mak +++ b/build-aux/version.mak @@ -1,2 +1,6 @@ VERSION=1.1dev +CHANNEL?=nightly RELEASE?=$(VERSION) +RELEASE_MASTER?=https://sortix.org/release +RELEASE_KEY=/etc/signify/sortix-$(VERSION).pub +RELEASE_SIG_URL?=$(RELEASE_MASTER)/$(CHANNEL)/$(HOST_MACHINE).sh.sig diff --git a/etc/signify/sortix-1.1.pub b/etc/signify/sortix-1.1.pub new file mode 100644 index 00000000..70bbd3ef --- /dev/null +++ b/etc/signify/sortix-1.1.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWQiTQbFzyZJVobf/pn53Jp3njhRB9DgwkMaNakCpDE9RaTABMjlbz9W diff --git a/etc/signify/sortix-1.1dev.pub b/etc/signify/sortix-1.1dev.pub new file mode 100644 index 00000000..19eeb69a --- /dev/null +++ b/etc/signify/sortix-1.1dev.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWQnkSm9lj1YIZYpt1Y3mHYzFsaky82gQF6CrW4lme9OoEYzSIl2ZsIC diff --git a/etc/signify/sortix-1.2.pub b/etc/signify/sortix-1.2.pub new file mode 100644 index 00000000..2d0c5ccc --- /dev/null +++ b/etc/signify/sortix-1.2.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWTGrBXmGvl2zUpCa47ui5EyPsnitKLjsCZ2YZphNY8F3b33t6QWYDs1 diff --git a/etc/signify/sortix-1.2dev.pub b/etc/signify/sortix-1.2dev.pub new file mode 100644 index 00000000..c3034752 --- /dev/null +++ b/etc/signify/sortix-1.2dev.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWRTbLQ+3+a9I5yche2BEVP03TRtumGO4Vgq1AQ/5bRj8JAJ1R0+vpxE diff --git a/share/man/man5/upgrade.conf.5 b/share/man/man5/upgrade.conf.5 index 1bf47e29..c2b35e8c 100644 --- a/share/man/man5/upgrade.conf.5 +++ b/share/man/man5/upgrade.conf.5 @@ -22,6 +22,10 @@ as part of .Xr installation 7 to match what was installed. .Pp +The file also controls the actions of +.Xr tix 8 +when upgrading releases and installing ports. +.Pp Developers may wish to customize what happens to .Pa /src on a system upgrade. @@ -45,7 +49,32 @@ Lines are supposed to contain assignments to variables. An assignment is the name of the variable, whitespace, an equal character, whitespace, the value, whitespace, and then the end of the line. .Bl -tag -width "12345678" -.It Sy grub Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) . +.It Sy channel Ns "=" Ns Ar channel +If the current release has an upgrade path named +.Ar channel +to a new release, +then system upgrades will upgrade to that new release. +If no such release path exists or if this variable is not set, upgrades will +continue to upgrade to the current release series. +Depending on the current release, the offically supported values are +.Sy stable +for stable releases and +.Sy nightly +for development releases. +Downgrading releases is not supported. For instance, if the current system is +a development release, specifying +.Sy stable +will not downgrade the system to the previous stable release, as no such upgrade +path exists. +Instead upgrades will upgrade to the next stable release when it becomes +available. +.It Sy force_mirror Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) +Use the preferred mirror set with +.Sy mirror +even if the file specified by +.Sy release_sig_url +does not list it. +.It Sy grub Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) States GRUB is used as the bootloader. If either the .Sy system @@ -57,7 +86,18 @@ then the bootloader is reinstalled .Xr ( grub-install 8 ) and updated .Xr ( update-grub 8 ) . -.It Sy newsrc Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) . +.It Sy mirror Ns "=" Ns Ar mirror +Download releases and ports from this preferred +.Ar mirror , +a URL to the top level directory of a mirror. +The mirror is only used if the file specified by +.Sy release_sig_url +lists this mirror, unless +.Sy force_mirror +is set to +.Sy yes . +If no mirror is set, a default mirror is used. +.It Sy newsrc Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) Place the new source code in .Pa /newsrc and move any existing @@ -69,17 +109,39 @@ This preserves the current directory. This takes precedence over and disables the behavior described under .Sy src . -.It Sy ports Ns "=" Ns Oo Sy no "|" yes Oc (default Sy yes ) . +.It Sy ports Ns "=" Ns Oo Sy no "|" yes Oc (default Sy yes ) Install the new ports. Ports that don't exist anymore will be removed. -.It Sy src Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) . +.It Sy release_key Ns "=" Ns Ar release_key +Verify the file specified by +.Sy release_sig_url +with the +.Xr signify 1 +public key file at the path +.Ar release_key . +This variable is updated during system upgrades and there is no need to change +this variable manually. +.It Sy release_sig_url Ns "=" Ns Ar release_sig_url +Download the meta-information about the current release from the URL +.Ar release_sig_url . +This file is verified with the +.Xr signify 1 +public key in the +.Sy release_key +variable. +The file describes the current release, provides checksums of all the published +files, lists all the supported mirrors, provides instructions on how to upgrade +to this release, and lists all the supported upgrade paths to new releases. +This variable is updated during system upgrades and there is no need to change +this variable manually. +.It Sy src Ns "=" Ns Oo Sy no "|" yes Oc (default Sy no ) Place the new source code in .Pa /src and move any existing .Pa /src into .Pa /oldsrc . -.It Sy system Ns "=" Ns Oo Sy no "|" yes Oc (default Sy yes ) . +.It Sy system Ns "=" Ns Oo Sy no "|" yes Oc (default Sy yes ) Install the new system. The upgrade hooks are run if needed as described in .Xr following-development 7 . @@ -96,6 +158,18 @@ then regenerate The defaults will be used if .Pa /etc/upgrade.conf is missing. +If +.Sy release_key +or +.Sy release_sig_url +are absent, +.Xr tix 8 +will not be able to upgrade the current system nor install ports. +If +.Sy channel +is absent, +.Xr tix 8 +will not upgrade to new releases. .Sh FILES .Bl -tag -width "/etc/upgrade.conf" -compact .It Pa /etc/upgrade.conf @@ -103,11 +177,12 @@ Upgrade configuration. .El .Sh EXAMPLES .Bd -literal -system = yes +grub = yes ports = yes src = no -grub = yes +system = yes .Ed .Sh SEE ALSO .Xr upgrade 7 , -.Xr sysupgrade 8 +.Xr sysupgrade 8 , +.Xr tix 8 diff --git a/sysinstall/conf.c b/sysinstall/conf.c index 1876398c..592c3ec1 100644 --- a/sysinstall/conf.c +++ b/sysinstall/conf.c @@ -37,6 +37,10 @@ void conf_init(struct conf* conf) void conf_free(struct conf* conf) { + free(conf->channel); + free(conf->mirror); + free(conf->release_key); + free(conf->release_sig_url); conf_init(conf); } @@ -60,12 +64,43 @@ static bool conf_assign(struct conf* conf, const char* path, off_t line_number) { - if ( !strcmp(name, "grub") ) + char* new_value; + if ( !strcmp(name, "channel") ) + { + if ( !(new_value = strdup(value)) ) + return false; + free(conf->channel); + conf->channel = new_value; + } + else if ( !strcmp(name, "force_mirror") ) + conf->force_mirror = conf_boolean(name, value, path, line_number); + else if ( !strcmp(name, "grub") ) conf->grub = conf_boolean(name, value, path, line_number); + else if ( !strcmp(name, "mirror") ) + { + if ( !(new_value = strdup(value)) ) + return false; + free(conf->mirror); + conf->mirror = new_value; + } else if ( !strcmp(name, "newsrc") ) conf->newsrc = conf_boolean(name, value, path, line_number); else if ( !strcmp(name, "ports") ) conf->ports = conf_boolean(name, value, path, line_number); + else if ( !strcmp(name, "release_key") ) + { + if ( !(new_value = strdup(value)) ) + return false; + free(conf->release_key); + conf->release_key = new_value; + } + else if ( !strcmp(name, "release_sig_url") ) + { + if ( !(new_value = strdup(value)) ) + return false; + free(conf->release_sig_url); + conf->release_sig_url = new_value; + } else if ( !strcmp(name, "src") ) conf->src = conf_boolean(name, value, path, line_number); else if ( !strcmp(name, "system") ) diff --git a/sysinstall/conf.h b/sysinstall/conf.h index 761df306..47c3f3a8 100644 --- a/sysinstall/conf.h +++ b/sysinstall/conf.h @@ -22,9 +22,14 @@ struct conf { + char* channel; + bool force_mirror; bool grub; + char* mirror; bool newsrc; bool ports; + char* release_key; + char* release_sig_url; bool src; bool system; }; diff --git a/sysinstall/sysinstall.c b/sysinstall/sysinstall.c index 0bc03013..d4d63d5c 100644 --- a/sysinstall/sysinstall.c +++ b/sysinstall/sysinstall.c @@ -494,6 +494,21 @@ int main(void) // TODO: You can leave this program by pressing ^C but it can leave your // system in an inconsistent state. + if ( conf.channel ) + install_configurationf("upgrade.conf", "a", "channel = %s\n", + conf.channel); + if ( conf.force_mirror != false ) + install_configurationf("upgrade.conf", "a", "force_mirror = %s\n", + conf.force_mirror ? "yes" : "no"); + if ( conf.mirror ) + install_configurationf("upgrade.conf", "a", "mirror = %s\n", + conf.mirror); + if ( conf.release_key ) + install_configurationf("upgrade.conf", "a", "release_key = %s\n", + conf.release_key); + if ( conf.release_sig_url ) + install_configurationf("upgrade.conf", "a", "release_sig_url = %s\n", + conf.release_sig_url); install_configurationf("upgrade.conf", "a", "src = yes\n"); // TODO: GUI support. diff --git a/sysinstall/sysmerge.c b/sysinstall/sysmerge.c index 773b3541..1e984155 100644 --- a/sysinstall/sysmerge.c +++ b/sysinstall/sysmerge.c @@ -330,6 +330,10 @@ int main(int argc, char* argv[]) if ( copy_files ) { + // TODO: Update /etc/upgrade.conf with new release values. + // TODO: What about native upgrades using make sysmerge? Should those + // values be updated then? Should there be an option to control + // this behavior? const char* target = ""; if ( wait ) { diff --git a/sysinstall/sysupgrade.c b/sysinstall/sysupgrade.c index 9b4bbcbf..12184d70 100644 --- a/sysinstall/sysupgrade.c +++ b/sysinstall/sysupgrade.c @@ -868,6 +868,7 @@ int main(void) } if ( conf.system ) upgrade_finalize(target_release, &new_release, "", "."); + // TODO: Update /etc/upgrade.conf with new release values. if ( conf.system ) { printf(" - Creating initrd...\n"); diff --git a/tix/Makefile b/tix/Makefile index ff714318..1323091c 100644 --- a/tix/Makefile +++ b/tix/Makefile @@ -26,11 +26,14 @@ tix-vars \ PROGRAMS:=\ $(BINARIES) \ +tix-clean \ tix-eradicate-libtool-la \ +tix-fetch \ tix-iso-add \ tix-iso-bootconfig \ tix-iso-liveconfig \ tix-port \ +tix-upgrade \ MANPAGES8=\ tix-build.8 \ diff --git a/tix/tix-clean b/tix/tix-clean new file mode 100755 index 00000000..2b1a81fc --- /dev/null +++ b/tix/tix-clean @@ -0,0 +1,68 @@ +#!/bin/sh +# Copyright (c) 2017 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-clean +# Remove temporary tix files. + +set -e + +collection="" +sysroot="" + +dashdash= +previous_option= +for argument do + if test -n "$previous_option"; then + eval $previous_option=\$argument + previous_option= + continue + fi + + case $argument in + *=?*) parameter=$(expr "X$argument" : '[^=]*=\(.*\)' || true) ;; + *=) parameter= ;; + *) parameter=yes ;; + esac + + case $dashdash$argument in + --) dashdash=yes ;; + -o) previous_option=output ;; + --collection=*) collection=$parameter ;; + --collection) previous_option=collection ;; + --sysroot=*) sysroot=$parameter ;; + --sysroot) previous_option=sysroot ;; + -*) echo "$0: unrecognized option $argument" >&2 + exit 1 ;; + *) + if [ $operand = 1 ]; then + input="$argument" + operand=2 + elif [ $operand = 2 ]; then + directory="$argument" + operand=3 + else + echo "$0: unexpected extra operand $argument" >&2 + exit 1 + fi + ;; + esac +done + +if test -n "$previous_option"; then + echo "$0: option '$argument' requires an argument" >&2 + exit 1 +fi + +rm -rf "$collection/var/cache/tix" diff --git a/tix/tix-fetch b/tix/tix-fetch new file mode 100755 index 00000000..b9cba320 --- /dev/null +++ b/tix/tix-fetch @@ -0,0 +1,424 @@ +#!/bin/sh +# Copyright (c) 2017, 2021 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 +port=false +porttix=false +release=false +sha256=false +sha256sum=false +source=false +source_full=false +srctix=false +sysroot="" +toolchain=false +url=false +url_master=false +url_master_release=false +url_mirror=false +url_mirror_release=false +url_release_sig=false +url_sha256sum=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) previous_option="-nv" ;; + --download-non-verbose) previous_option="-v" ;; + --download-quiet) previous_option="-q" ;; + --download-verbose) previous_option="-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 ;; + --patch) patch=true ;; + --port) port=true ;; + --porttix) porttix=true ;; + --release) release=true ;; + --sha256) sha256=true ;; + --sha256sum) sha256sum=true ;; + --source-full) source_full=true ;; + --source) source=true ;; + --srctix) srctix=true ;; + --sysroot) previous_option=sysroot ;; + --sysroot=*) sysroot=$parameter ;; + --toolchain) toolchain=true ;; + --url) url=true ;; + --url-master) url_master=true ;; + --url-mirror) url_mirror=true ;; + --url-master-release) url_master_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() { + grep -E "^$1[[:space:]]*=" -- "$collection/etc/upgrade.conf" | + tail -n 1 | + sed -E 's/^[^=]*=[[:space:]]*(|.*[^[:space:]])[[:space:]]*/\1/' +} + +tmpdir=$(mktemp -dt tix-fetch-port.XXXXXX) +trap 'rm -rf -- "$tmpdir"' EXIT HUP INT QUIT TERM + +RELEASE_KEY=$(conf release_key) +RELEASE_SIG_URL=$(conf release_sig_url) +PREFERRED_MIRROR=$(conf mirror) +FORCE_MIRROR=$(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. +if [ -z "$input_release_file" ]; then + if [ -z "$input_release_sig_file" ]; then + (cd "$tmpdir" && + do_wget -U "$USER_AGENT" $wget_options -O release.sh.sig \ + -- "$RELEASE_SIG_URL") + else + cp -T -- "$input_release_sig_file" "$tmpdir/release.sh.sig" + fi + signify -Vq -p "$RELEASE_KEY" -em "$tmpdir/release.sh" +else + cp -T -- "$input_release_file" "$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 + +# Load the release description. +# TODO: SECURITY: REMOTE CODE EXECUTION OF SIGNED REMOTE CODE. +# TODO: SECURITY: Protect against responding with older release.sh. +. "`realpath -- "$tmpdir/release.sh"`" # Avoid PATH search with absolute path. + +if $url_master; then + printf "%s\n" "$MASTER" + exit +elif $url_master_release; then + printf "%s\n" "$MASTER/$RELEASE" + exit +fi + +# Default to the master mirror but switch to the preferred mirror if the release +# description knows about the mirror and believes it to be trustworthy. +MIRROR="$MASTER" +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" = yes ]; then + MIRROR="$PREFERRED_MIRROR" + else + echo "$0: warning: ignoring unsupported mirror $PREFERRED_MIRROR" >&2 + fi +fi + +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 master. + (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' +} + +# TODO: Remove: +#escape_extended_regex_test_self() { +# printf "%s\n" "$1" | grep -E "^$(escape_extended_regex "$1")\$" +#} + +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 master. + (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 + 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 --port then fetch the port by the requested name. + if $port; then + REQUEST="$REQUEST.tix.tar.xz" + REQUESTDIR="repository/$MACHINE-sortix/" + elif $boot; then + REQUEST="$REQUEST" + REQUESTDIR="$MACHINE/boot/" + elif $initrd; then + REQUEST="$REQUEST.initrd.xz" + REQUESTDIR="$MACHINE/boot/" + elif $patch; then + REQUEST="$REQUEST.patch" + REQUESTDIR="patches/" + elif $normalize; then + REQUEST="$REQUEST.normalize" + REQUESTDIR="patches/" + elif $srctix; then + REQUEST="$REQUEST.srctix.tar.xz" + REQUESTDIR="srctix/" + elif $porttix; then + REQUEST="$REQUEST.porttix.tar.xz" + REQUESTDIR="porttix/" + elif $toolchain; then + REQUESTDIR="toolchain/" + else + REQUESTDIR="" + fi + request "$REQUEST" "$REQUESTDIR" +done diff --git a/tix/tix-upgrade b/tix/tix-upgrade new file mode 100755 index 00000000..77b20eb9 --- /dev/null +++ b/tix/tix-upgrade @@ -0,0 +1,230 @@ +#!/bin/sh +# Copyright (c) 2017, 2021 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-upgrade +# Upgrade operating system and ports. + +set -e + +cachedir="" +clean=false +collection="" +download_only=false +fetch_options= +ports_only=false +sysroot="" +upgrade_ports=false +upgrade_system=false +wait="" + +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 ;; + -w) wait=-w ;; + --cachedir=*) cachedir=$parameter ;; + --cachedir) previous_option=cachedir ;; + --clean) clean=true ;; + --collection=*) collection=$parameter ;; + --collection) previous_option=collection ;; + --download-only) download_only=true ;; + --insecure-downgrade-to-http) fetch_options="$fetch_options $argument" ;; + --insecure-no-check-certificate) fetch_options="$fetch_options $argument" ;; + --ports) upgrade_ports=true ;; + --system) upgrade_system=true ;; + --sysroot) previous_option=sysroot ;; + --sysroot=*) sysroot=$parameter ;; + --wait) wait=--wait ;; + -*) 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: Reject additional operands. + +if [ -z "$collection" ]; then + collection="$sysroot" +fi + +if [ -n "$collection" ]; then + collection=$(cd "$collection" && pwd) +fi + +if ! $upgrade_ports && ! $upgrade_system; then + upgrade_ports=true + upgrade_system=true +fi + +# Update only ports if this isn't a whole system installation. +if [ ! -e "$collection/tix/manifest/system" ]; then + ports_only=true +fi + +if [ -z "$cachedir" ]; then + cachedir="$collection/var/cache/tix" +fi + +if $clean; then + echo "Removing cache directory: $cachedir" + rm -rf -- "$cachedir" +fi + +mkdir -p -- "$cachedir" +mkdir -p -- "$cachedir/new" + +# Fetch latest release.sig.sh from the master and its matching sha256sum file. +tix-fetch $fetch_options \ + --output-release-sig-file="$cachedir/new/release.sh.sig" \ + --output-sha256sum="$cachedir/new/sha256sum" + +# If release.sig.sh or sha256sum changed, clean the cache directory of downloads +# that were currently in progress as they might not have the right checksums. +# TODO: This requires diff(1) in the base system! Avoid that. Get a cmp(1)! +if ! diff -- "$cachedir/release.sh.sig" \ + "$cachedir/new/release.sh.sig" 1>/dev/null 2>/dev/null || + ! diff -- "$cachedir/sha256sum" \ + "$cachedir/new/sha256sum" 1>/dev/null 2>/dev/null; then + rm -rf -- "$cachedir/boot" + rm -rf -- "$cachedir/repository" + rm -rf -- "$cachedir/sysroot" +fi + +# Store the new release.sig.sh and sha256sum files so we can resume the download +# if cancelled and these files still match. +mv -- "$cachedir/new/release.sh.sig" "$cachedir/release.sh.sig" +mv -- "$cachedir/new/sha256sum" "$cachedir/sha256sum" +rm -rf -- "$cachedir/new" + +mkdir -p "$cachedir/boot" +mkdir -p "$cachedir/repository" + +if $upgrade_system; then + # Fetch the base system initrds from the mirror. + # TODO: What about the system source code in /src? + # TODO: Get the overlay initrd if it exists. + tix-fetch $fetch_options \ + --input-release-sig-file="$cachedir/release.sh.sig" \ + --input-sha256sum="$cachedir/sha256sum" \ + -c --initrd -O "$cachedir/boot" -- system +fi + +if $upgrade_ports; then + # Fetch each port from the mirror. + ports=$(LC_ALL=C sort -- "$collection/tix/installed.list") + # TODO: This fails if the port does not exist upstream. + mkdir -p "$cachedir/repository" + for port in $ports; do + # The port has a hash if if it exists upstream. + sha256=$(tix-fetch $fetch_options \ + --input-release-sig-file="$cachedir/release.sh.sig" \ + --input-sha256sum="$cachedir/sha256sum" \ + --sha256 --port -- $port) + # If the port exists upstream, get the latest version. + if [ -n "$sha256" ]; then + tix-fetch $fetch_options \ + --input-release-sig-file="$cachedir/release.sh.sig" \ + --input-sha256sum="$cachedir/sha256sum" \ + -c --port -O "$cachedir/repository" -- $port + fi + done +fi + +# Stop if only downloading. +if $download_only; then + exit +fi + +rm -rf -- "$cachedir/sysroot" +mkdir -p -- "$cachedir/sysroot" + +if $upgrade_system; then + # Extract the base system into the sysroot. + rm -rf -- "$cachedir/sysroot" + mkdir -p -- "$cachedir/sysroot" + for initrd in system overlay; do + initrd_path="$cachedir/boot/$initrd.initrd" + if [ -e "$initrd_path.xz" ]; then + echo "Extracting $initrd.initrd.xz..." + unxz -k "$initrd_path.xz" + initrdfs "$initrd_path" extract -C "$cachedir/sysroot" / + rm -f "$initrd_path.xz" + fi + done +fi + +# TODO: These install all the ports in "$cachedir/repository" but the directory +# might not have been cleared, it could contain unverified ports. +if $upgrade_system; then + # Do a full system upgrade using the sysroot and the new ports. + # TODO: If sysmerge -w, such as on ABI upgrades, the e2fsprogs package is not + # installed in the system root and the new initrd might not contain a + # /sbin/fsck.ext2 and the system can become bricked if it needs a fsck + # on boot. + # TODO: There's no support here for doing a sysmerge on a given sysroot. + if $upgrade_ports; then + # TODO: Get the architecture from somewhere else than /etc/machine? + tix-collection "$cachedir/sysroot" create \ + --platform="$(cat -- "$sysroot/etc/machine")-sortix" \ + --prefix= --generation=2 + tix-install --collection="$cachedir/sysroot" -- "$cachedir/repository/"* + full=--full + else + # TODO: Is this case supported? + full= + fi + sysmerge $full $wait "$cachedir/sysroot" +elif $upgrade_ports; then + # Upgrade to each of the new ports. + # TODO: tix-install --reinstall is unsupported due to lack of support for + # files moving between manifests or being deleted. + tix-install --reinstall -- "$cachedir/repository/"* +fi + +rm -rf -- "$cachedir/boot" +rm -rf -- "$cachedir/repository" +rm -rf -- "$cachedir/sysroot" + +#tmp= +#trap '[ -n "$tmp" ] && rm -rf "$tmp"' EXIT HUP INT QUIT TERM +#tmp=$(mktemp -dt upgrade.XXXXXX) +#mkdir -p "$tmp/tmp" +#export TMPDIR="$tmp/tmp" +#(cd "$tmp" && tix-fetch --release -o sortix.iso) +#mkdir -p "$tmp/sortix.iso.d" +#echo "Extracting sortix.iso..." +#xorriso -osirrox on -indev "$tmp/sortix.iso" -extract / "$tmp/sortix.iso.d" > /dev/null 2> /dev/null +#rm -f "$tmp/sortix.iso" diff --git a/tix/tix.c b/tix/tix.c index bed658ad..b1fba6ad 100644 --- a/tix/tix.c +++ b/tix/tix.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2015, 2016 Jonas 'Sortie' Termansen. + * Copyright (c) 2013, 2015, 2016, 2017 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 @@ -170,6 +170,8 @@ static void version(FILE* fp, const char* argv0) int main(int argc, char* argv[]) { + const char* tmp = getenv_def("TMPDIR", "/tmp"); + params_t params; memset(¶ms, 0, sizeof(params)); params.collection = NULL; @@ -245,7 +247,7 @@ int main(int argc, char* argv[]) errx(1, "error: no command specified."); const char* cmd = argv[1]; - if ( !strcmp(cmd, "install") ) + if ( !strcmp(cmd, "old-install") ) { if ( argc == 2 ) errx(1, "expected list of packages to install after `install'"); @@ -276,6 +278,45 @@ int main(int argc, char* argv[]) return 0; } + else if ( !strcmp(cmd, "install") ) + { + initialize_tmp(tmp, "tix-install"); + if ( chdir(tmp_root) < 0 ) + err(1, "%s", tmp_root); + + if ( fork_and_wait_or_death() ) + { + char** fetch_argv = malloc(sizeof(char*) * (5 + (argc-2) + 1)); + fetch_argv[0] = (char*) "tix-fetch"; + fetch_argv[1] = (char*) "--collection"; + fetch_argv[2] = params.collection; + fetch_argv[3] = (char*) "--port"; + fetch_argv[4] = (char*) "--"; + int offset = 5; + for ( int i = 0; i < argc-2; i++ ) + fetch_argv[offset + i] = argv[2 + i]; + fetch_argv[offset + argc-2] = NULL; + execvp(fetch_argv[0], (char* const*) fetch_argv); + err(127, "`%s'", fetch_argv[0]); + } + for ( int i = 2; i < argc; i++ ) + { + const char* pkg_name = argv[i]; + char* pkg_path = print_string("%s%s", pkg_name, ".tix.tar.xz"); + if ( fork_and_wait_or_death() ) + { + const char* install_argv[] = + { + "tix-install", + "--collection", params.collection, + "--", pkg_path, + NULL + }; + execvp(install_argv[0], (char* const*) install_argv); + err(127, "`%s'", install_argv[0]); + } + } + } else { fprintf(stderr, "%s: unknown command: `%s'\n", argv0, cmd);