diff --git a/.gitignore b/.gitignore index 1df3692ee..766d876e8 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,6 @@ secrets.db /ENVIRONMENT.txt /GIT_HASH.txt /VERSION.txt -/eos-*.tar.gz /*.deb /target /*.squashfs diff --git a/Makefile b/Makefile index 02328a1d8..dbe10d677 100644 --- a/Makefile +++ b/Makefile @@ -120,7 +120,6 @@ install: $(ALL_TARGETS) $(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/startd) $(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-cli) $(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-sdk) - $(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/embassy-cli) if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-musl/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then $(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); fi @@ -164,15 +163,16 @@ wormhole-deb: results/$(BASENAME).deb update: $(ALL_TARGETS) @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi - $(call ssh,"sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/") - $(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/embassy/next PLATFORM=$(PLATFORM) - $(call ssh,'sudo NO_SYNC=1 /media/embassy/next/usr/lib/startos/scripts/chroot-and-upgrade "apt-get install -y $(shell cat ./build/lib/depends)"') + $(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create') + $(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM) + $(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync "apt-get install -y $(shell cat ./build/lib/depends)"') emulate-reflash: $(ALL_TARGETS) @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi - $(call ssh,"sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/") - $(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/embassy/next PLATFORM=$(PLATFORM) - $(call ssh,"sudo touch /media/embassy/config/upgrade && sudo rm -f /media/embassy/config/disk.guid && sudo sync && sudo reboot") + $(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create') + $(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM) + $(call ssh,'sudo rm -f /media/startos/config/disk.guid') + $(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync "apt-get install -y $(shell cat ./build/lib/depends)"') upload-ota: results/$(BASENAME).squashfs TARGET=$(TARGET) KEY=$(KEY) ./upload-ota.sh diff --git a/build/README.md b/build/README.md deleted file mode 100644 index 3bab01866..000000000 --- a/build/README.md +++ /dev/null @@ -1,107 +0,0 @@ -# Building StartOS - -⚠️ The commands given assume a Debian or Ubuntu-based environment. _Building in -a VM is NOT yet supported_ ⚠️ - -## Prerequisites - -1. Install dependencies - -- Avahi - - `sudo apt install -y avahi-daemon` - - Installed by default on most Debian systems - https://avahi.org -- Build Essentials (needed to run `make`) - - `sudo apt install -y build-essential` -- Docker - - `curl -fsSL https://get.docker.com | sh` - - https://docs.docker.com/get-docker - - Add your user to the docker group: `sudo usermod -a -G docker $USER` - - Reload user environment `exec sudo su -l $USER` -- Prepare Docker environment - - Setup buildx (https://docs.docker.com/buildx/working-with-buildx/) - - Create a builder: `docker buildx create --use` - - Add multi-arch build ability: - `docker run --rm --privileged linuxkit/binfmt:v0.8` -- Node Version 12+ - - snap: `sudo snap install node` - - [nvm](https://github.com/nvm-sh/nvm#installing-and-updating): - `nvm install --lts` - - https://nodejs.org/en/docs -- NPM Version 7+ - - apt: `sudo apt install -y npm` - - [nvm](https://github.com/nvm-sh/nvm#installing-and-updating): - `nvm install --lts` - - https://docs.npmjs.com/downloading-and-installing-node-js-and-npm -- jq - - `sudo apt install -y jq` - - https://stedolan.github.io/jq -- yq - - snap: `sudo snap install yq` - - binaries: https://github.com/mikefarah/yq/releases/ - - https://mikefarah.gitbook.io/yq - -2. Clone the latest repo with required submodules - > :information_source: You chan check latest available version - > [here](https://github.com/Start9Labs/start-os/releases) - ``` - git clone --recursive https://github.com/Start9Labs/start-os.git --branch latest - ``` - -## Build Raspberry Pi Image - -``` -cd start-os -make embassyos-raspi.img ARCH=aarch64 -``` - -## Flash - -Flash the resulting `embassyos-raspi.img` to your SD Card - -We recommend [Balena Etcher](https://www.balena.io/etcher/) - -## Setup - -Visit http://start.local from any web browser - We recommend -[Firefox](https://www.mozilla.org/firefox/browsers) - -Enter your product key. This is generated during the build process and can be -found in `product_key.txt`, located in the root directory. - -## Troubleshooting - -1. I just flashed my SD card, fired up StartOS, bootup sounds and all, but my - browser is saying "Unable to connect" with start.local. - -- Try doing a hard refresh on your browser, or opening the url in a - private/incognito window. If you've ran an instance of StartOS before, - sometimes you can have a stale cache that will block you from navigating to - the page. - -2. Flashing the image isn't working with balenaEtcher. I'm getting - `Cannot read property 'message' of null` when I try. - -- The latest versions of Balena may not flash properly. This version here: - https://github.com/balena-io/etcher/releases/tag/v1.5.122 should work - properly. - -3. Startup isn't working properly and I'm curious as to why. How can I view logs - regarding startup for debugging? - -- Find the IP of your device -- Run `nc 8080` and it will print the logs - -4. I need to ssh into my server to fix something, but I cannot get to the - console to add ssh keys normally. - -- During the Build step, instead of running just - `make embassyos-raspi.img ARCH=aarch64` run - `ENVIRONMENT=dev make embassyos-raspi.img ARCH=aarch64`. Flash like normal, - and insert into your server. Boot up StartOS, then on another computer on - the same network, ssh into the the server with the username `start9` password - `embassy`. - -4. I need to reset my password, how can I do that? - -- You will need to reflash your device. Select "Use Existing Drive" once you are - in setup, and it will prompt you to set a new password. diff --git a/build/RELEASE.md b/build/RELEASE.md deleted file mode 100644 index bdbecc00e..000000000 --- a/build/RELEASE.md +++ /dev/null @@ -1,76 +0,0 @@ -# Release Process - -## `embassyos_0.3.x-1_amd64.deb` - -- Description: debian package for x86_64 - intended to be installed on pureos -- Destination: GitHub Release Tag -- Requires: N/A -- Build steps: - - Clone `https://github.com/Start9Labs/embassy-os-deb` at `master` - - Run `make TAG=master` from that folder -- Artifact: `./embassyos_0.3.x-1_amd64.deb` - -## `eos---_amd64.iso` - -- Description: live usb image for x86_64 -- Destination: GitHub Release Tag -- Requires: `embassyos_0.3.x-1_amd64.deb` -- Build steps: - - Clone `https://github.com/Start9Labs/eos-image-recipes` at `master` - - Copy `embassyos_0.3.x-1_amd64.deb` to - `overlays/vendor/root/embassyos_0.3.x-1_amd64.deb` - - Run `./run-local-build.sh byzantium` from that folder -- Artifact: `./results/eos---_amd64.iso` - -## `eos.x86_64.squashfs` - -- Description: compressed embassyOS x86_64 filesystem image -- Destination: GitHub Release Tag, Registry @ - `resources/eos//eos.x86_64.squashfs` -- Requires: `eos---_amd64.iso` -- Build steps: - - From `https://github.com/Start9Labs/eos-image-recipes` at `master` - - `./extract-squashfs.sh results/eos---_amd64.iso` -- Artifact: `./results/eos.x86_64.squashfs` - -## `eos.raspberrypi.squashfs` - -- Description: compressed embassyOS raspberrypi filesystem image -- Destination: GitHub Release Tag, Registry @ - `resources/eos//eos.raspberrypi.squashfs` -- Requires: N/A -- Build steps: - - Clone `https://github.com/Start9Labs/embassy-os` at `master` - - `make embassyos-raspi.img` - - flash `embassyos-raspi.img` to raspberry pi - - boot raspberry pi with ethernet - - wait for chime - - you can watch logs using `nc 8080` - - unplug raspberry pi, put sd card back in build machine - - `./build/raspberry-pi/rip-image.sh` -- Artifact: `./eos.raspberrypi.squashfs` - -## `lite-upgrade.img` - -- Description: update image for users coming from 0.3.2.1 and before -- Destination: Registry @ `resources/eos//eos.img` -- Requires: `eos.raspberrypi.squashfs` -- Build steps: - - From `https://github.com/Start9Labs/embassy-os` at `master` - - `make lite-upgrade.img` -- Artifact `./lite-upgrade.img` - -## `eos---_raspberrypi.tar.gz` - -- Description: pre-initialized raspberrypi image -- Destination: GitHub Release Tag (as tar.gz) -- Requires: `eos.raspberrypi.squashfs` -- Build steps: - - From `https://github.com/Start9Labs/embassy-os` at `master` - - `make eos_raspberrypi.img` - - `tar --format=posix -cS -f- eos---_raspberrypi.img | gzip > eos---_raspberrypi.tar.gz` -- Artifact `./eos---_raspberrypi.tar.gz` - -## `embassy-sdk` - -- Build and deploy to all registries \ No newline at end of file diff --git a/build/dpkg-deps/depends b/build/dpkg-deps/depends index 5209b5421..38d9b1a58 100644 --- a/build/dpkg-deps/depends +++ b/build/dpkg-deps/depends @@ -34,7 +34,6 @@ network-manager nvme-cli nyx openssh-server -podman postgresql psmisc qemu-guest-agent diff --git a/build/lib/scripts/chroot-and-upgrade b/build/lib/scripts/chroot-and-upgrade index a7fafb8bc..ef3208806 100755 --- a/build/lib/scripts/chroot-and-upgrade +++ b/build/lib/scripts/chroot-and-upgrade @@ -5,44 +5,104 @@ if [ "$UID" -ne 0 ]; then exit 1 fi +POSITIONAL_ARGS=() + +while [[ $# -gt 0 ]]; do + case $1 in + --no-sync) + NO_SYNC=1 + shift + ;; + --create) + ONLY_CREATE=1 + shift + ;; + -*|--*) + echo "Unknown option $1" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") # save positional arg + shift # past argument + ;; + esac +done + +set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters + if [ -z "$NO_SYNC" ]; then echo 'Syncing...' - rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next + umount -R /media/startos/next 2> /dev/null + rm -rf /media/startos/upper /media/startos/next + mkdir /media/startos/upper + mount -t tmpfs tmpfs /media/startos/upper + mkdir -p /media/startos/upper/data /media/startos/upper/work /media/startos/next + mount -t overlay \ + -olowerdir=/media/startos/current,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \ + overlay /media/startos/next fi -mkdir -p /media/embassy/next/run -mkdir -p /media/embassy/next/dev -mkdir -p /media/embassy/next/sys -mkdir -p /media/embassy/next/proc -mkdir -p /media/embassy/next/boot -mount --bind /run /media/embassy/next/run -mount --bind /tmp /media/embassy/next/tmp -mount --bind /dev /media/embassy/next/dev -mount --bind /sys /media/embassy/next/sys -mount --bind /proc /media/embassy/next/proc -mount --bind /boot /media/embassy/next/boot +if [ -n "$ONLY_CREATE" ]; then + exit 0 +fi + +mkdir -p /media/startos/next/run +mkdir -p /media/startos/next/dev +mkdir -p /media/startos/next/sys +mkdir -p /media/startos/next/proc +mkdir -p /media/startos/next/boot +mount --bind /run /media/startos/next/run +mount --bind /tmp /media/startos/next/tmp +mount --bind /dev /media/startos/next/dev +mount --bind /sys /media/startos/next/sys +mount --bind /proc /media/startos/next/proc +mount --bind /boot /media/startos/next/boot if [ -z "$*" ]; then - chroot /media/embassy/next + chroot /media/startos/next CHROOT_RES=$? else - chroot /media/embassy/next "$SHELL" -c "$*" + chroot /media/startos/next "$SHELL" -c "$*" CHROOT_RES=$? fi -umount /media/embassy/next/run -umount /media/embassy/next/tmp -umount /media/embassy/next/dev -umount /media/embassy/next/sys -umount /media/embassy/next/proc -umount /media/embassy/next/boot +umount /media/startos/next/run +umount /media/startos/next/tmp +umount /media/startos/next/dev +umount /media/startos/next/sys +umount /media/startos/next/proc +umount /media/startos/next/boot if [ "$CHROOT_RES" -eq 0 ]; then + + if [ -h /media/startos/config/current.rootfs ] && [ -e /media/startos/config/current.rootfs ]; then + echo 'Pruning...' + current="$(readlink -f /media/startos/config/current.rootfs)" + needed=$(du -s --bytes /media/startos/next | awk '{print $1}') + while [[ "$(df -B1 --output=avail --sync /media/startos/images | tail -n1)" -lt "$needed" ]]; do + to_prune="$(ls -t1 /media/startos/images/*.rootfs | grep -v "$current" | tail -n1)" + if [ -e "$to_prune" ]; then + echo " Pruning $to_prune" + rm -rf "$to_prune" + else + >&2 echo "Not enough space and nothing to prune!" + exit 1 + fi + done + echo 'done.' + fi + echo 'Upgrading...' - touch /media/embassy/config/upgrade + time mksquashfs /media/startos/next /media/startos/images/next.squashfs -b 4096 -comp gzip + hash=$(start-cli util b3sum /media/startos/images/next.squashfs | head -c 32) + mv /media/startos/images/next.squashfs /media/startos/images/${hash}.rootfs + ln -rsf /media/startos/images/${hash}.rootfs /media/startos/config/current.rootfs sync reboot -fi \ No newline at end of file +fi + +umount -R /media/startos/next +rm -rf /media/startos/upper /media/startos/next \ No newline at end of file diff --git a/build/lib/scripts/embassy-initramfs-module b/build/lib/scripts/embassy-initramfs-module deleted file mode 100755 index 2a2f08a07..000000000 --- a/build/lib/scripts/embassy-initramfs-module +++ /dev/null @@ -1,98 +0,0 @@ -# Local filesystem mounting -*- shell-script -*- - -# -# This script overrides local_mount_root() in /scripts/local -# and mounts root as a read-only filesystem with a temporary (rw) -# overlay filesystem. -# - -. /scripts/local - -local_mount_root() -{ - echo 'using embassy initramfs module' - - local_top - local_device_setup "${ROOT}" "root file system" - ROOT="${DEV}" - - # Get the root filesystem type if not set - if [ -z "${ROOTFSTYPE}" ]; then - FSTYPE=$(get_fstype "${ROOT}") - else - FSTYPE=${ROOTFSTYPE} - fi - - local_premount - - # CHANGES TO THE ORIGINAL FUNCTION BEGIN HERE - # N.B. this code still lacks error checking - - modprobe ${FSTYPE} - checkfs ${ROOT} root "${FSTYPE}" - - ROOTFLAGS="$(echo "${ROOTFLAGS}" | sed 's/subvol=\(next\|current\)//' | sed 's/^-o *$//')" - - if [ "${FSTYPE}" != "unknown" ]; then - mount -t ${FSTYPE} ${ROOTFLAGS} ${ROOT} ${rootmnt} - else - mount ${ROOTFLAGS} ${ROOT} ${rootmnt} - fi - - echo 'mounting embassyfs' - - mkdir /embassyfs - - mount --move ${rootmnt} /embassyfs - - if ! [ -d /embassyfs/current ] && [ -d /embassyfs/prev ]; then - mv /embassyfs/prev /embassyfs/current - fi - - if ! [ -d /embassyfs/current ]; then - mkdir /embassyfs/current - for FILE in $(ls /embassyfs); do - if [ "$FILE" != current ]; then - mv /embassyfs/$FILE /embassyfs/current/ - fi - done - fi - - mkdir -p /embassyfs/config - - if [ -f /embassyfs/config/upgrade ] && [ -d /embassyfs/next ]; then - mv /embassyfs/current /embassyfs/prev - mv /embassyfs/next /embassyfs/current - rm /embassyfs/config/upgrade - fi - - if ! [ -d /embassyfs/next ]; then - if [ -d /embassyfs/prev ]; then - mv /embassyfs/prev /embassyfs/next - else - mkdir /embassyfs/next - fi - fi - - mkdir /lower /upper - - mount -r --bind /embassyfs/current /lower - - modprobe overlay || insmod "/lower/lib/modules/$(uname -r)/kernel/fs/overlayfs/overlay.ko" - - # Mount a tmpfs for the overlay in /upper - mount -t tmpfs tmpfs /upper - mkdir /upper/data /upper/work - - # Mount the final overlay-root in $rootmnt - mount -t overlay \ - -olowerdir=/lower,upperdir=/upper/data,workdir=/upper/work \ - overlay ${rootmnt} - - mkdir -p ${rootmnt}/media/embassy/config - mount --bind /embassyfs/config ${rootmnt}/media/embassy/config - mkdir -p ${rootmnt}/media/embassy/next - mount --bind /embassyfs/next ${rootmnt}/media/embassy/next - mkdir -p ${rootmnt}/media/embassy/embassyfs - mount -r --bind /embassyfs ${rootmnt}/media/embassy/embassyfs -} \ No newline at end of file diff --git a/build/lib/scripts/grub-probe-eos b/build/lib/scripts/grub-probe-eos index ed37eefaa..aa2e1cacc 100755 --- a/build/lib/scripts/grub-probe-eos +++ b/build/lib/scripts/grub-probe-eos @@ -3,8 +3,8 @@ ARGS= for ARG in $@; do - if [ -d "/media/embassy/embassyfs" ] && [ "$ARG" = "/" ]; then - ARG=/media/embassy/embassyfs + if [ -d "/media/startos/root" ] && [ "$ARG" = "/" ]; then + ARG=/media/startos/root fi ARGS="$ARGS $ARG" done diff --git a/build/lib/scripts/startos-initramfs-module b/build/lib/scripts/startos-initramfs-module new file mode 100755 index 000000000..e13c887e2 --- /dev/null +++ b/build/lib/scripts/startos-initramfs-module @@ -0,0 +1,114 @@ +# Local filesystem mounting -*- shell-script -*- + +# +# This script overrides local_mount_root() in /scripts/local +# and mounts root as a read-only filesystem with a temporary (rw) +# overlay filesystem. +# + +. /scripts/local + +local_mount_root() +{ + echo 'using startos initramfs module' + + local_top + local_device_setup "${ROOT}" "root file system" + ROOT="${DEV}" + + # Get the root filesystem type if not set + if [ -z "${ROOTFSTYPE}" ]; then + FSTYPE=$(get_fstype "${ROOT}") + else + FSTYPE=${ROOTFSTYPE} + fi + + local_premount + + # CHANGES TO THE ORIGINAL FUNCTION BEGIN HERE + # N.B. this code still lacks error checking + + modprobe ${FSTYPE} + checkfs ${ROOT} root "${FSTYPE}" + + echo 'mounting startos' + mkdir /startos + + ROOTFLAGS="$(echo "${ROOTFLAGS}" | sed 's/subvol=\(next\|current\)//' | sed 's/^-o *$//')" + + if [ "${FSTYPE}" != "unknown" ]; then + mount -t ${FSTYPE} ${ROOTFLAGS} ${ROOT} /startos + else + mount ${ROOTFLAGS} ${ROOT} /startos + fi + + if [ -d /startos/images ]; then + if [ -h /startos/config/current.rootfs ] && [ -e /startos/config/current.rootfs ]; then + image=$(readlink -f /startos/config/current.rootfs) + else + image="$(ls -t1 /startos/images/*.rootfs | head -n1)" + fi + if ! [ -f "$image" ]; then + >&2 echo "image $image not available to boot" + exit 1 + fi + else + if [ -f /startos/config/upgrade ] && [ -d /startos/next ]; then + oldroot=/startos/next + elif [ -d /startos/current ]; then + oldroot=/startos/current + elif [ -d /startos/prev ]; then + oldroot=/startos/prev + else + >&2 echo no StartOS filesystem found + exit 1 + fi + + mkdir -p /startos/config/overlay/etc + mv $oldroot/etc/fstab /startos/config/overlay/etc/fstab + mv $oldroot/etc/machine-id /startos/config/overlay/etc/machine-id + mv $oldroot/etc/ssh /startos/config/overlay/etc/ssh + + mkdir -p /startos/images + mv $oldroot /startos/images/legacy.rootfs + + rm -rf /startos/next /startos/current /startos/prev + + ln -rsf /startos/images/old.squashfs /startos/config/current.rootfs + image=$(readlink -f /startos/config/current.rootfs) + fi + + mkdir /lower /upper + + if [ -d "$image" ]; then + mount -r --bind $image /lower + elif [ -f "$image" ]; then + modprobe squashfs + mount -r $image /lower + else + >&2 echo "not a regular file or directory: $image" + exit 1 + fi + + modprobe overlay || insmod "/lower/lib/modules/$(uname -r)/kernel/fs/overlayfs/overlay.ko" + + # Mount a tmpfs for the overlay in /upper + mount -t tmpfs tmpfs /upper + mkdir /upper/data /upper/work + + mkdir -p /startos/config/overlay + + # Mount the final overlay-root in $rootmnt + mount -t overlay \ + -olowerdir=/startos/config/overlay:/lower,upperdir=/upper/data,workdir=/upper/work \ + overlay ${rootmnt} + + mkdir -p ${rootmnt}/media/startos/config + mount --bind /startos/config ${rootmnt}/media/startos/config + mkdir -p ${rootmnt}/media/startos/images + mount --bind /startos/images ${rootmnt}/media/startos/images + mkdir -p ${rootmnt}/media/startos/root + mount -r --bind /startos ${rootmnt}/media/startos/root + mkdir -p ${rootmnt}/media/startos/current + mount -r --bind /lower ${rootmnt}/media/startos/current +} \ No newline at end of file diff --git a/build/raspberrypi/make-image.sh b/build/raspberrypi/make-image.sh index 3b07cb3a8..ec5ea4297 100755 --- a/build/raspberrypi/make-image.sh +++ b/build/raspberrypi/make-image.sh @@ -63,7 +63,7 @@ sudo unsquashfs -f -d $TMPDIR startos.raspberrypi.squashfs REAL_GIT_HASH=$(cat $TMPDIR/usr/lib/startos/GIT_HASH.txt) REAL_VERSION=$(cat $TMPDIR/usr/lib/startos/VERSION.txt) REAL_ENVIRONMENT=$(cat $TMPDIR/usr/lib/startos/ENVIRONMENT.txt) -sudo sed -i 's| boot=embassy| init=/usr/lib/startos/scripts/init_resize\.sh|' $TMPDIR/boot/cmdline.txt +sudo sed -i 's| boot=startos| init=/usr/lib/startos/scripts/init_resize\.sh|' $TMPDIR/boot/cmdline.txt sudo cp ./build/raspberrypi/fstab $TMPDIR/etc/ sudo cp ./build/raspberrypi/init_resize.sh $TMPDIR/usr/lib/startos/scripts/init_resize.sh sudo umount $TMPDIR/boot diff --git a/core/Cargo.lock b/core/Cargo.lock index a93a77e1e..920390d65 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -38,23 +38,23 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.14", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.2.11", + "getrandom 0.2.14", "once_cell", "version_check", "zerocopy", @@ -62,9 +62,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -86,9 +86,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-tzdata" @@ -107,47 +107,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -155,9 +156,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "arrayref" @@ -193,9 +194,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.4" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f658e2baef915ba0f26f1f7c42bfb8e12f532a01f449a090ded75ae7a07e9ba2" +checksum = "4e9eabd7a98fe442131a17c316bd9349c43695e49e730c3c8e12cfb5f4da2693" dependencies = [ "brotli", "flate2", @@ -224,18 +225,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -260,9 +261,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" @@ -275,9 +276,9 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", - "http 0.2.11", - "http-body 0.4.5", - "hyper 0.14.27", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", "itoa", "matchit", "memchr", @@ -300,13 +301,13 @@ checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core 0.4.3", - "base64 0.21.5", + "base64 0.21.7", "bytes", "futures-util", "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "hyper-util", "itoa", "matchit", @@ -320,7 +321,7 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper 1.0.0", + "sync_wrapper 1.0.1", "tokio", "tokio-tungstenite", "tower", @@ -338,8 +339,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 0.2.11", - "http-body 0.4.5", + "http 0.2.12", + "http-body 0.4.6", "mime", "rustversion", "tower-layer", @@ -378,7 +379,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "hyper-util", "pin-project-lite", "tokio", @@ -388,9 +389,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -415,15 +416,15 @@ checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" [[package]] name = "base64" -version = "0.13.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.21.5" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -433,9 +434,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-cookies" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb53b6b315f924c7f113b162e53b3901c05fc9966baf84d201dfcc7432a4bb38" +checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" dependencies = [ "lalrpop", "lalrpop-util", @@ -474,18 +475,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] [[package]] name = "bitmaps" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703642b98a00b3b90513279a8ede3fcfa479c126c5fb46e78f3051522f021403" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" [[package]] name = "bitvec" @@ -512,15 +513,17 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", + "memmap2", + "rayon", ] [[package]] @@ -543,9 +546,9 @@ dependencies = [ [[package]] name = "brotli" -version = "3.4.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -554,9 +557,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +checksum = "e6221fe77a248b9117d431ad93761222e1cf8ff282d9d1d5d9f53d6299a1cf76" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -564,9 +567,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -576,18 +579,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.84" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" -dependencies = [ - "libc", -] +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" [[package]] name = "cfg-if" @@ -597,9 +597,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -607,7 +607,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -616,14 +616,14 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ - "hashbrown 0.14.2", + "hashbrown 0.14.5", ] [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -632,18 +632,18 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", - "half", + "half 2.4.1", ] [[package]] @@ -665,21 +665,6 @@ dependencies = [ "inout", ] -[[package]] -name = "clap" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex 0.2.4", - "indexmap 1.9.3", - "strsim 0.10.0", - "termcolor", - "textwrap", -] - [[package]] name = "clap" version = "4.5.4" @@ -698,8 +683,8 @@ checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.0", - "strsim 0.11.0", + "clap_lex", + "strsim 0.11.1", ] [[package]] @@ -711,16 +696,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.39", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", + "syn 2.0.60", ] [[package]] @@ -731,9 +707,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "color-eyre" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" dependencies = [ "backtrace", "color-spantrace", @@ -746,9 +722,9 @@ dependencies = [ [[package]] name = "color-spantrace" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ "once_cell", "owo-colors", @@ -758,30 +734,30 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "concurrent-queue" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode 0.3.6", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -823,9 +799,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" @@ -881,9 +857,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd91cf61412820176e137621345ee43b3f4423e589e7ae4e50d601d93e35ef8" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "time", "version_check", @@ -908,9 +884,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -918,24 +894,24 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] @@ -948,41 +924,55 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-queue" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crossterm" @@ -990,7 +980,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "crossterm_winapi", "futures-core", "libc", @@ -1018,9 +1008,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f85c3514d2a6e64160359b45a3918c3b4178bcbf4ae5d03ab2d02e521c479a" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1089,9 +1079,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -1112,14 +1102,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "darling" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ "darling_core", "darling_macro", @@ -1127,40 +1117,40 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", @@ -1169,9 +1159,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", @@ -1190,12 +1180,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.9.0" @@ -1252,30 +1236,30 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "drain" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f1a0abf3fcefad9b4dd0e414207a7408e12b68414a01e6bb19b897d5bd7632d" +checksum = "9d105028bd2b5dfcb33318fd79a445001ead36004dd8dffef1bdd7e493d8bc1e" dependencies = [ "tokio", ] [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest 0.10.7", "elliptic-curve", "rfc6979", - "signature 2.0.0", + "signature 2.2.0", "spki", ] @@ -1296,7 +1280,7 @@ checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", "serde", - "signature 2.0.0", + "signature 2.2.0", ] [[package]] @@ -1319,30 +1303,30 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "ed25519 2.2.3", "rand_core 0.6.4", "serde", "sha2 0.10.8", - "signature 2.0.0", + "signature 2.2.0", "subtle", "zeroize", ] [[package]] name = "either" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" dependencies = [ "serde", ] [[package]] name = "elliptic-curve" -version = "0.13.6" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", @@ -1392,9 +1376,9 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -1408,7 +1392,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -1419,12 +1403,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1446,9 +1430,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "eyre" -version = "0.6.8" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", @@ -1456,9 +1440,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fd-lock-rs" @@ -1481,20 +1465,20 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.3" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f69037fe1b785e84986b4f2cbcf647381876a00671d25ceef715d7812dd7e1dd" +checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", ] [[package]] @@ -1511,9 +1495,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -1553,9 +1537,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -1577,9 +1561,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1592,9 +1576,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1602,15 +1586,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1630,38 +1614,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1699,9 +1683,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -1710,9 +1694,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gpt" @@ -1720,7 +1704,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8283e7331b8c93b9756e0cfdbcfb90312852f953c6faf9bf741e684cc3b6ad69" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "crc", "log", "uuid", @@ -1739,17 +1723,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.11", - "indexmap 1.9.3", + "http 0.2.12", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1758,9 +1742,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" dependencies = [ "bytes", "fnv", @@ -1768,7 +1752,7 @@ dependencies = [ "futures-sink", "futures-util", "http 1.1.0", - "indexmap 2.1.0", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1777,9 +1761,19 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "hashbrown" @@ -1793,16 +1787,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", ] [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "allocator-api2", ] @@ -1812,16 +1806,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.2", + "hashbrown 0.14.5", ] [[package]] name = "hdrhistogram" -version = "7.5.3" +version = "7.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b38e5c02b7c7be48c8dc5217c4f1634af2ea221caae2e024bffc7a7651c691" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" dependencies = [ - "base64 0.13.1", + "base64 0.21.7", "byteorder", "flate2", "nom", @@ -1852,7 +1846,7 @@ dependencies = [ "lazy_async_pool", "models", "pin-project", - "rpc-toolkit 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rpc-toolkit", "serde", "serde_json", "tokio", @@ -1871,9 +1865,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1883,15 +1877,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hifijson" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ef6b41c333e6dd2a4aaa59125a19b633cd17e7aaf372b2260809777bcdef4a" +checksum = "18ae468bcb4dfecf0e4949ee28abbc99076b6a0077f51ddbc94dbfff8e6a870c" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -1907,18 +1901,18 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1938,12 +1932,12 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http 0.2.11", + "http 0.2.12", "pin-project-lite", ] @@ -1990,22 +1984,22 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2 0.3.21", - "http 0.2.11", - "http-body 0.4.5", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -2014,14 +2008,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.3", + "h2 0.4.4", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -2030,6 +2024,7 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", ] [[package]] @@ -2038,7 +2033,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.27", + "hyper 0.14.28", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -2046,15 +2041,18 @@ dependencies = [ [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "hyper 0.14.27", + "http-body-util", + "hyper 1.3.1", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", ] [[package]] @@ -2064,20 +2062,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", + "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.0", - "hyper 1.2.0", + "hyper 1.3.1", "pin-project-lite", - "socket2 0.5.5", + "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -2131,6 +2133,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "imbl" version = "2.0.3" @@ -2147,9 +2159,9 @@ dependencies = [ [[package]] name = "imbl-sized-chunks" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6957ea0b2541c5ca561d3ef4538044af79f8a05a1eb3a3b148936aaceaa1076" +checksum = "144006fb58ed787dcae3f54575ff4349755b00ccc99f4b4873860b654be1ed63" dependencies = [ "bitmaps", ] @@ -2157,7 +2169,7 @@ dependencies = [ [[package]] name = "imbl-value" version = "0.1.0" -source = "git+https://github.com/Start9Labs/imbl-value.git#929395141c3a882ac366c12ac9402d0ebaa2201b" +source = "git+https://github.com/Start9Labs/imbl-value.git#48dc39a762a3b4f9300d3b9f850cbd394e777ae0" dependencies = [ "imbl", "serde", @@ -2204,20 +2216,20 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.5", "serde", ] [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", "instant", @@ -2276,15 +2288,21 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi 0.3.3", - "rustix", - "windows-sys 0.48.0", + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "isocountry" version = "0.3.2" @@ -2333,9 +2351,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jaq-core" @@ -2343,7 +2361,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb52eeac20f256459e909bd4a03bb8c4fab6a1fdbb8ed52d00f644152df48ece" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", "dyn-clone", "hifijson", "indexmap 1.9.3", @@ -2377,12 +2395,12 @@ dependencies = [ [[package]] name = "josekit" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5754487a088f527b1407df470db8e654e4064dccbbe1fe850e0773721e9962b7" +checksum = "0953340cf63354cec4a385f1fbcb3f409a5823778cae236078892f6030ed4565" dependencies = [ "anyhow", - "base64 0.21.5", + "base64 0.21.7", "flate2", "once_cell", "openssl", @@ -2395,9 +2413,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -2435,42 +2453,42 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[package]] name = "lalrpop" -version = "0.19.12" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" dependencies = [ "ascii-canvas", "bit-set", - "diff", "ena", - "is-terminal", - "itertools 0.10.5", + "itertools 0.11.0", "lalrpop-util", "petgraph", + "pico-args", "regex", - "regex-syntax 0.6.29", + "regex-syntax 0.8.3", "string_cache", "term", "tiny-keccak", "unicode-xid", + "walkdir", ] [[package]] name = "lalrpop-util" -version = "0.19.12" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex", + "regex-automata 0.4.6", ] [[package]] @@ -2500,9 +2518,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.150" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libm" @@ -2512,20 +2530,19 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "libc", - "redox_syscall 0.4.1", ] [[package]] name = "libsqlite3-sys" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" dependencies = [ "cc", "pkg-config", @@ -2534,15 +2551,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -2550,9 +2567,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "matchers" @@ -2594,9 +2611,18 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] [[package]] name = "memoffset" @@ -2630,18 +2656,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -2653,7 +2679,8 @@ dependencies = [ name = "models" version = "0.1.0" dependencies = [ - "base64 0.21.5", + "axum 0.7.5", + "base64 0.21.7", "color-eyre", "ed25519-dalek 2.1.1", "emver", @@ -2666,7 +2693,7 @@ dependencies = [ "rand 0.8.5", "regex", "reqwest", - "rpc-toolkit 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rpc-toolkit", "serde", "serde_json", "sqlx", @@ -2699,9 +2726,9 @@ dependencies = [ [[package]] name = "new_debug_unreachable" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "new_mime_guess" @@ -2744,7 +2771,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "cfg-if", "libc", ] @@ -2771,9 +2798,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41" dependencies = [ "num-bigint", "num-complex", @@ -2813,28 +2840,33 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" dependencies = [ "num-traits", ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -2855,9 +2887,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -2869,29 +2901,29 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.3", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "num_enum" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -2902,9 +2934,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -2917,17 +2949,17 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssh-keys" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75a0ec2d1b302412fb503224289325fcc0e44600176864804c7211b055cfd58" +checksum = "e9939566c441a6542e87c74310d4ac713c17679c76f296932e732f405bc25e3d" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "byteorder", "md-5", "sha2 0.10.8", @@ -2936,11 +2968,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.59" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -2957,7 +2989,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -2968,18 +3000,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.1.6+3.1.4" +version = "300.2.3+3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" +checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.95" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -2988,12 +3020,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - [[package]] name = "overload" version = "0.1.1" @@ -3031,10 +3057,24 @@ dependencies = [ ] [[package]] -name = "parking_lot" -version = "0.12.1" +name = "p521" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2 0.10.8", +] + +[[package]] +name = "parking_lot" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -3042,15 +3082,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -3074,7 +3114,7 @@ dependencies = [ "nix 0.26.4", "patch-db-macro", "serde", - "serde_cbor 0.11.1", + "serde_cbor", "thiserror", "tokio", "tracing", @@ -3132,7 +3172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.1.0", + "indexmap 2.2.6", ] [[package]] @@ -3145,30 +3185,36 @@ dependencies = [ ] [[package]] -name = "pin-project" -version = "1.1.3" +name = "pico-args" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -3199,21 +3245,21 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "platforms" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "portable-atomic" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" [[package]] name = "powerfmt" @@ -3249,27 +3295,27 @@ dependencies = [ [[package]] name = "primeorder" -version = "0.13.3" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7dbe9ed3b56368bd99483eb32fe9c17fdd3730aebadc906918ce78d54c7eeb4" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", ] [[package]] name = "proc-macro-crate" -version = "2.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit 0.20.7", + "toml_edit 0.21.1", ] [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -3282,13 +3328,13 @@ checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.4.1", + "bitflags 2.5.0", "lazy_static", "num-traits", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", "rusty-fork", "tempfile", "unarray", @@ -3307,9 +3353,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" +checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" dependencies = [ "bytes", "prost-derive", @@ -3317,22 +3363,22 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" +checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "prost-types" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e081b29f63d83a4bc75cfc9f3fe424f9156cf92d8a4f0c9407cce9a1b67327cf" +checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" dependencies = [ "prost", ] @@ -3361,9 +3407,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -3433,7 +3479,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.14", ] [[package]] @@ -3463,6 +3509,26 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -3478,15 +3544,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -3497,26 +3554,35 @@ dependencies = [ ] [[package]] -name = "redox_users" -version = "0.4.4" +name = "redox_syscall" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "getrandom 0.2.11", + "bitflags 2.5.0", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom 0.2.14", "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", - "regex-syntax 0.8.2", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", ] [[package]] @@ -3530,13 +3596,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -3547,28 +3613,30 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.21.5", + "base64 0.22.1", "bytes", "cookie 0.17.0", "cookie_store", "encoding_rs", "futures-core", "futures-util", - "h2 0.3.21", - "http 0.2.11", - "http-body 0.4.5", - "hyper 0.14.27", + "h2 0.4.4", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -3577,7 +3645,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 2.1.2", "serde", "serde_json", "serde_urlencoded", @@ -3598,9 +3666,9 @@ dependencies = [ [[package]] name = "reqwest_cookie_store" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba529055ea150e42e4eb9c11dcd380a41025ad4d594b0cb4904ef28b037e1061" +checksum = "93ea5c6f30c19d766efe8d823c88f9abd1c56516648a0d4264ab2dc04cc19472" dependencies = [ "bytes", "cookie_store", @@ -3620,16 +3688,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.5" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", - "getrandom 0.2.11", + "cfg-if", + "getrandom 0.2.14", "libc", "spin 0.9.8", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3646,34 +3715,12 @@ dependencies = [ [[package]] name = "rpc-toolkit" version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48252a30abb9426a3239fa8dfd2c8dd2647bb24db0b6145db2df04ae53fe647" -dependencies = [ - "clap 3.2.25", - "futures", - "hyper 0.14.27", - "lazy_static", - "openssl", - "reqwest", - "rpc-toolkit-macro 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde", - "serde_cbor 0.11.2", - "serde_json", - "thiserror", - "tokio", - "url", - "yajrc", -] - -[[package]] -name = "rpc-toolkit" -version = "0.2.3" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#c89e0abdb15dd3bed9adb5339cf0b61a96f32b50" +source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/no-dyn-ctx#f5566840bb9af0743612fede4034f5a390cd1eee" dependencies = [ "async-stream", "async-trait", "axum 0.7.5", - "clap 4.5.4", + "clap", "futures", "http 1.1.0", "http-body-util", @@ -3684,7 +3731,6 @@ dependencies = [ "openssl", "pin-project", "reqwest", - "rpc-toolkit-macro 0.2.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits)", "serde", "serde_json", "thiserror", @@ -3694,54 +3740,11 @@ dependencies = [ "yajrc", ] -[[package]] -name = "rpc-toolkit-macro" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8e4b9cb00baf2d61bcd35e98d67dcb760382a3b4540df7e63b38d053c8a7b8b" -dependencies = [ - "proc-macro2", - "rpc-toolkit-macro-internals 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.109", -] - -[[package]] -name = "rpc-toolkit-macro" -version = "0.2.2" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#c89e0abdb15dd3bed9adb5339cf0b61a96f32b50" -dependencies = [ - "proc-macro2", - "rpc-toolkit-macro-internals 0.2.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits)", - "syn 1.0.109", -] - -[[package]] -name = "rpc-toolkit-macro-internals" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e2ce21b936feaecdab9c9a8e75b9dca64374ccc11951a58045ad6559b75f42" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rpc-toolkit-macro-internals" -version = "0.2.2" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits#c89e0abdb15dd3bed9adb5339cf0b61a96f32b50" -dependencies = [ - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "rsa" -version = "0.9.3" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ "const-oid", "digest 0.10.7", @@ -3752,7 +3755,7 @@ dependencies = [ "pkcs8", "rand_core 0.6.4", "sha2 0.10.8", - "signature 2.0.0", + "signature 2.2.0", "spki", "subtle", "zeroize", @@ -3770,11 +3773,11 @@ dependencies = [ [[package]] name = "rust-argon2" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e71971821b3ae0e769e4a4328dbcb517607b434db7697e9aba17203ec14e46a" +checksum = "9d9848531d60c9cbbcf9d166c885316c24bc0e2a9d3eba0956bb6cbbd79bc6e8" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "blake2b_simd", "constant_time_eq", ] @@ -3796,22 +3799,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.8" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "ring", "rustls-webpki 0.101.7", @@ -3820,14 +3823,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.2", + "rustls-webpki 0.102.3", "subtle", "zeroize", ] @@ -3838,14 +3841,24 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" [[package]] name = "rustls-webpki" @@ -3859,9 +3872,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" dependencies = [ "ring", "rustls-pki-types", @@ -3870,9 +3883,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "rusty-fork" @@ -3904,17 +3917,26 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3949,9 +3971,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -3962,9 +3984,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -3972,18 +3994,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.192" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -4001,38 +4023,28 @@ dependencies = [ name = "serde_cbor" version = "0.11.1" dependencies = [ - "half", - "serde", -] - -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", + "half 1.8.3", "serde", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -4040,9 +4052,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", @@ -4050,9 +4062,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -4071,16 +4083,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.4.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" dependencies = [ - "base64 0.21.5", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.1.0", + "indexmap 2.2.6", "serde", + "serde_derive", "serde_json", "serde_with_macros", "time", @@ -4088,23 +4101,23 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.4.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "serde_yaml" -version = "0.9.27" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -4194,9 +4207,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -4209,9 +4222,9 @@ checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "signature" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", @@ -4251,22 +4264,12 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.4.10" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" -dependencies = [ - "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4286,9 +4289,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -4296,20 +4299,20 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" dependencies = [ - "itertools 0.11.0", + "itertools 0.12.1", "nom", "unicode_categories", ] [[package]] name = "sqlx" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" dependencies = [ "sqlx-core", "sqlx-macros", @@ -4320,18 +4323,17 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "atoi", "byteorder", "bytes", "chrono", "crc", "crossbeam-queue", - "dotenvy", "either", "event-listener", "futures-channel", @@ -4341,14 +4343,14 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.1.0", + "indexmap 2.2.6", "log", "memchr", "once_cell", "paste", "percent-encoding", - "rustls 0.21.8", - "rustls-pemfile", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", "serde", "serde_json", "sha2 0.10.8", @@ -4364,9 +4366,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" dependencies = [ "proc-macro2", "quote", @@ -4377,9 +4379,9 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ "dotenvy", "either", @@ -4403,13 +4405,13 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", - "base64 0.21.5", - "bitflags 2.4.1", + "base64 0.21.7", + "bitflags 2.5.0", "byteorder", "bytes", "chrono", @@ -4446,13 +4448,13 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", - "base64 0.21.5", - "bitflags 2.4.1", + "base64 0.21.7", + "bitflags 2.5.0", "byteorder", "chrono", "crc", @@ -4474,7 +4476,6 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "sha1", "sha2 0.10.8", "smallvec", "sqlx-core", @@ -4486,9 +4487,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" dependencies = [ "atoi", "chrono", @@ -4505,6 +4506,7 @@ dependencies = [ "sqlx-core", "tracing", "url", + "urlencoding", ] [[package]] @@ -4530,7 +4532,7 @@ dependencies = [ "quote", "regex-syntax 0.6.29", "strsim 0.10.0", - "syn 2.0.39", + "syn 2.0.60", "unicode-width", ] @@ -4557,18 +4559,19 @@ dependencies = [ [[package]] name = "ssh-key" -version = "0.6.2" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2180b3bc4955efd5661a97658d3cf4c8107e0d132f619195afe9486c13cca313" +checksum = "ca9b366a80cf18bb6406f4cf4d10aebfb46140a8c0c33f666a144c5c76ecbafc" dependencies = [ "ed25519-dalek 2.1.1", "p256", "p384", + "p521", "rand_core 0.6.4", "rsa", "sec1", "sha2 0.10.8", - "signature 2.0.0", + "signature 2.2.0", "ssh-cipher", "ssh-encoding", "subtle", @@ -4586,18 +4589,18 @@ dependencies = [ "axum 0.7.5", "axum-server", "base32", - "base64 0.21.5", + "base64 0.21.7", "base64ct", "basic-cookies", "blake3", "bytes", "chrono", "ciborium", - "clap 4.5.4", + "clap", "color-eyre", "console", "console-subscriber", - "cookie 0.18.0", + "cookie 0.18.1", "cookie_store", "current_platform", "digest 0.10.7", @@ -4613,11 +4616,12 @@ dependencies = [ "hex", "hmac", "http 1.1.0", + "http-body-util", "id-pool", "imbl", "imbl-value", "include_dir", - "indexmap 2.1.0", + "indexmap 2.2.6", "indicatif", "integer-encoding", "ipnet", @@ -4656,12 +4660,13 @@ dependencies = [ "reqwest", "reqwest_cookie_store", "rpassword", - "rpc-toolkit 0.2.3 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=refactor/traits)", + "rpc-toolkit", "rust-argon2", "rustyline-async", "semver", "serde", "serde_json", + "serde_urlencoded", "serde_with", "serde_yaml", "sha2 0.10.8", @@ -4680,7 +4685,7 @@ dependencies = [ "tokio-tar", "tokio-tungstenite", "tokio-util", - "toml 0.8.8", + "toml 0.8.12", "torut", "tracing", "tracing-error", @@ -4741,9 +4746,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -4764,9 +4769,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -4781,9 +4786,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[package]] name = "system-configuration" @@ -4820,20 +4825,19 @@ checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" dependencies = [ "filetime", "libc", - "xattr 1.0.1", + "xattr 1.3.1", ] [[package]] name = "tempfile" -version = "3.8.1" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.1", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4856,17 +4860,11 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "thingbuf" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4706f1bfb859af03f099ada2de3cea3e515843c2d3e93b7893f16d94a37f9415" +checksum = "662b54ef6f7b4e71f683dadc787bbb2d8e8ef2f91b682ebed3164a5a7abca905" dependencies = [ "parking_lot", "pin-project", @@ -4874,22 +4872,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -4905,9 +4903,9 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -4915,12 +4913,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -4935,10 +4934,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -4980,7 +4980,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", "tracing", "windows-sys 0.48.0", @@ -5004,7 +5004,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -5023,7 +5023,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.22.3", + "rustls 0.22.4", "rustls-pki-types", "tokio", ] @@ -5042,9 +5042,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -5108,14 +5108,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.8" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.21.0", + "toml_edit 0.22.12", ] [[package]] @@ -5133,35 +5133,35 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.7", ] [[package]] @@ -5173,12 +5173,12 @@ dependencies = [ "async-stream", "async-trait", "axum 0.6.20", - "base64 0.21.5", + "base64 0.21.7", "bytes", - "h2 0.3.21", - "http 0.2.11", - "http-body 0.4.5", - "hyper 0.14.27", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-timeout", "percent-encoding", "pin-project", @@ -5197,7 +5197,7 @@ version = "0.2.1" source = "git+https://github.com/Start9Labs/torut.git?branch=update/dependencies#cc7a1425a01214465e106975e6690794d8551bdb" dependencies = [ "base32", - "base64 0.21.5", + "base64 0.21.7", "derive_more", "ed25519-dalek 1.0.1", "hex", @@ -5262,7 +5262,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -5337,9 +5337,9 @@ dependencies = [ [[package]] name = "treediff" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" +checksum = "4d127780145176e2b5d16611cc25a900150e86e9fd79d3bde6ff3a37359c9cb5" dependencies = [ "serde_json", ] @@ -5393,14 +5393,14 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ts-rs" version = "8.1.0" -source = "git+https://github.com/dr-bonez/ts-rs.git?branch=feature/top-level-as#d5f359d803158e06f47977a6f785878a82221d4f" +source = "git+https://github.com/dr-bonez/ts-rs.git?branch=feature/top-level-as#7ae88ade90b5e724159048a663a0bdb04bed27f7" dependencies = [ "thiserror", "ts-rs-macros", @@ -5409,12 +5409,12 @@ dependencies = [ [[package]] name = "ts-rs-macros" version = "8.1.0" -source = "git+https://github.com/dr-bonez/ts-rs.git?branch=feature/top-level-as#d5f359d803158e06f47977a6f785878a82221d4f" +source = "git+https://github.com/dr-bonez/ts-rs.git?branch=feature/top-level-as#7ae88ade90b5e724159048a663a0bdb04bed27f7" dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", "termcolor", ] @@ -5440,22 +5440,22 @@ dependencies = [ [[package]] name = "typed-builder" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444d8748011b93cb168770e8092458cb0f8854f931ff82fdf6ddfbd72a9c933e" +checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352" +checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -5481,9 +5481,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -5493,24 +5493,24 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode-xid" @@ -5526,9 +5526,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "unsafe-libyaml" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "untrusted" @@ -5538,12 +5538,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna 0.4.0", + "idna 0.5.0", "percent-encoding", "serde", ] @@ -5568,11 +5568,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.5.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.14", ] [[package]] @@ -5602,6 +5602,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -5624,10 +5634,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.88" +name = "wasite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -5635,24 +5651,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -5662,9 +5678,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5672,22 +5688,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-streams" @@ -5704,9 +5720,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -5714,18 +5730,19 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.24.0" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" -dependencies = [ - "rustls-webpki 0.101.7", -] +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "whoami" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "wasite", +] [[package]] name = "winapi" @@ -5745,11 +5762,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -5760,20 +5777,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.52.5", ] [[package]] @@ -5791,22 +5799,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.5", ] [[package]] @@ -5826,25 +5819,20 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -5853,15 +5841,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -5871,15 +5853,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -5889,15 +5865,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] -name = "windows_i686_msvc" -version = "0.42.2" +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -5907,15 +5883,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -5925,15 +5895,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -5943,15 +5907,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -5961,24 +5919,33 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.5.19" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.50.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", "windows-sys 0.48.0", @@ -6004,11 +5971,13 @@ dependencies = [ [[package]] name = "xattr" -version = "1.0.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", + "linux-raw-sys", + "rustix", ] [[package]] @@ -6029,7 +5998,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f355ab62ebe30b758c1f4ab096a306722c4b7dbfb9d8c07d18c70d71a945588" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "hashbrown 0.13.2", "lazy_static", "serde", @@ -6037,29 +6006,29 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.25" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.25" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -6072,5 +6041,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] diff --git a/core/build-prod.sh b/core/build-prod.sh index 8b6184942..f81ddb093 100755 --- a/core/build-prod.sh +++ b/core/build-prod.sh @@ -28,7 +28,7 @@ set +e fail= echo "FEATURES=\"$FEATURES\"" echo "RUSTFLAGS=\"$RUSTFLAGS\"" -if ! rust-musl-builder sh -c "(cd core && cargo build --release $(if [ -n "$FEATURES" ]; then echo "--features $FEATURES"; fi) --locked --bin startbox --target=$ARCH-unknown-linux-musl)"; then +if ! rust-musl-builder sh -c "(cd core && cargo build --release --no-default-features --features cli,daemon,$FEATURES --locked --bin startbox --target=$ARCH-unknown-linux-musl)"; then fail=true fi if ! rust-musl-builder sh -c "(cd core && cargo build --release --no-default-features --features container-runtime,$FEATURES --locked --bin containerbox --target=$ARCH-unknown-linux-musl)"; then diff --git a/core/build-reg.sh b/core/build-reg.sh new file mode 100755 index 000000000..9928b6714 --- /dev/null +++ b/core/build-reg.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +cd "$(dirname "${BASH_SOURCE[0]}")" + +set -e +shopt -s expand_aliases + +if [ -z "$ARCH" ]; then + ARCH=$(uname -m) +fi + +USE_TTY= +if tty -s; then + USE_TTY="-it" +fi + +cd .. +FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')" +RUSTFLAGS="" + +if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then + RUSTFLAGS="--cfg tokio_unstable" +fi + +alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl' + +set +e +fail= +echo "FEATURES=\"$FEATURES\"" +echo "RUSTFLAGS=\"$RUSTFLAGS\"" +if ! rust-musl-builder sh -c "(cd core && cargo build --release --no-default-features --features cli,registry,$FEATURES --locked --bin registrybox --target=$ARCH-unknown-linux-musl)"; then + fail=true +fi +set -e +cd core + +sudo chown -R $USER target +sudo chown -R $USER ~/.cargo + +if [ -n "$fail" ]; then + exit 1 +fi diff --git a/core/helpers/Cargo.toml b/core/helpers/Cargo.toml index 228f3ef54..9af19018e 100644 --- a/core/helpers/Cargo.toml +++ b/core/helpers/Cargo.toml @@ -11,7 +11,7 @@ futures = "0.3.28" lazy_async_pool = "0.3.3" models = { path = "../models" } pin-project = "1.1.3" -rpc-toolkit = "0.2.3" +rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "refactor/no-dyn-ctx" } serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0" tokio = { version = "1", features = ["full"] } diff --git a/core/install-cli.sh b/core/install-cli.sh index f4fe712ee..620600d92 100755 --- a/core/install-cli.sh +++ b/core/install-cli.sh @@ -12,4 +12,4 @@ if [ -z "$PLATFORM" ]; then export PLATFORM=$(uname -m) fi -cargo install --path=./startos --no-default-features --features=cli,docker --bin start-cli --locked +cargo install --path=./startos --no-default-features --features=cli,docker,registry --bin start-cli --locked diff --git a/core/models/Cargo.toml b/core/models/Cargo.toml index 3611f45d5..76c66b4f2 100644 --- a/core/models/Cargo.toml +++ b/core/models/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +axum = "0.7.5" base64 = "0.21.4" color-eyre = "0.6.2" ed25519-dalek = { version = "2.0.0", features = ["serde"] } @@ -22,8 +23,8 @@ patch-db = { version = "*", path = "../../patch-db/patch-db", features = [ ] } rand = "0.8.5" regex = "1.10.2" -reqwest = "0.11.22" -rpc-toolkit = "0.2.2" +reqwest = "0.12" +rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "refactor/no-dyn-ctx" } serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0" sqlx = { version = "0.7.2", features = [ diff --git a/core/models/src/errors.rs b/core/models/src/errors.rs index 2362b6dba..340fd15ca 100644 --- a/core/models/src/errors.rs +++ b/core/models/src/errors.rs @@ -1,9 +1,10 @@ use std::fmt::{Debug, Display}; +use axum::http::uri::InvalidUri; +use axum::http::StatusCode; use color_eyre::eyre::eyre; use num_enum::TryFromPrimitive; use patch_db::Revision; -use rpc_toolkit::hyper::http::uri::InvalidUri; use rpc_toolkit::reqwest; use rpc_toolkit::yajrc::{ RpcError, INVALID_PARAMS_ERROR, INVALID_REQUEST_ERROR, METHOD_NOT_FOUND_ERROR, PARSE_ERROR, @@ -207,6 +208,13 @@ impl Error { } } } +impl axum::response::IntoResponse for Error { + fn into_response(self) -> axum::response::Response { + let mut res = axum::Json(RpcError::from(self)).into_response(); + *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + res + } +} impl From for Error { fn from(value: std::convert::Infallible) -> Self { match value {} diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index cded2ab41..d88b51f96 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -22,7 +22,7 @@ name = "startos" path = "src/lib.rs" [[bin]] -name = "containerbox" +name = "startbox" path = "src/main.rs" [[bin]] @@ -30,14 +30,19 @@ name = "start-cli" path = "src/main.rs" [[bin]] -name = "startbox" +name = "containerbox" +path = "src/main.rs" + +[[bin]] +name = "registrybox" path = "src/main.rs" [features] cli = [] container-runtime = [] daemon = [] -default = ["cli", "daemon"] +registry = [] +default = ["cli", "daemon", "registry"] dev = [] unstable = ["console-subscriber", "tokio/tracing"] docker = [] @@ -58,7 +63,7 @@ base32 = "0.4.0" base64 = "0.21.4" base64ct = "1.6.0" basic-cookies = "0.1.4" -blake3 = "1.5.0" +blake3 = { version = "1.5.0", features = ["mmap", "rayon"] } bytes = "1" chrono = { version = "0.4.31", features = ["serde"] } clap = "4.4.12" @@ -89,6 +94,7 @@ helpers = { path = "../helpers" } hex = "0.4.3" hmac = "0.12.1" http = "1.0.0" +http-body-util = "0.1" id-pool = { version = "0.2.2", default-features = false, features = [ "serde", "u16", @@ -134,10 +140,10 @@ proptest = "1.3.1" proptest-derive = "0.4.0" rand = { version = "0.8.5", features = ["std"] } regex = "1.10.2" -reqwest = { version = "0.11.23", features = ["stream", "json", "socks"] } -reqwest_cookie_store = "0.6.0" +reqwest = { version = "0.12.4", features = ["stream", "json", "socks"] } +reqwest_cookie_store = "0.7.0" rpassword = "7.2.0" -rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "refactor/traits" } +rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "refactor/no-dyn-ctx" } rust-argon2 = "2.0.0" rustyline-async = "0.4.1" semver = { version = "1.0.20", features = ["serde"] } @@ -145,6 +151,7 @@ serde = { version = "1.0", features = ["derive", "rc"] } serde_cbor = { package = "ciborium", version = "0.2.1" } serde_json = "1.0" serde_toml = { package = "toml", version = "0.8.2" } +serde_urlencoded = "0.7" serde_with = { version = "3.4.0", features = ["macros", "json"] } serde_yaml = "0.9.25" sha2 = "0.10.2" diff --git a/core/startos/proptest-regressions/s9pk/merkle_archive/test.txt b/core/startos/proptest-regressions/s9pk/merkle_archive/test.txt new file mode 100644 index 000000000..116de6aba --- /dev/null +++ b/core/startos/proptest-regressions/s9pk/merkle_archive/test.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc dbb4790c31f9e400ed29a9ba2dbd61e3c55ce8a3fbae16601ca3512e803020ed # shrinks to files = [] diff --git a/core/startos/src/action.rs b/core/startos/src/action.rs index 396e7ed50..87ff317f8 100644 --- a/core/startos/src/action.rs +++ b/core/startos/src/action.rs @@ -1,7 +1,6 @@ use clap::Parser; pub use models::ActionId; use models::PackageId; -use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; diff --git a/core/startos/src/auth.rs b/core/startos/src/auth.rs index 915ac10bd..4838f2ea2 100644 --- a/core/startos/src/auth.rs +++ b/core/startos/src/auth.rs @@ -4,9 +4,10 @@ use chrono::{DateTime, Utc}; use clap::Parser; use color_eyre::eyre::eyre; use imbl_value::{json, InternedString}; +use itertools::Itertools; use josekit::jwk::Jwk; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{command, from_fn_async, AnyContext, CallRemote, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; @@ -82,7 +83,7 @@ impl std::str::FromStr for PasswordType { }) } } -pub fn auth() -> ParentHandler { +pub fn auth() -> ParentHandler { ParentHandler::new() .subcommand( "login", @@ -94,11 +95,11 @@ pub fn auth() -> ParentHandler { .subcommand( "logout", from_fn_async(logout) - .with_metadata("get-session", Value::Bool(true)) - .with_remote_cli::() - .no_display(), + .with_metadata("get_session", Value::Bool(true)) + .no_display() + .with_call_remote::(), ) - .subcommand("session", session()) + .subcommand("session", session::()) .subcommand( "reset-password", from_fn_async(reset_password_impl).no_cli(), @@ -112,7 +113,7 @@ pub fn auth() -> ParentHandler { from_fn_async(get_pubkey) .with_metadata("authenticated", Value::Bool(false)) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } @@ -128,26 +129,20 @@ fn gen_pwd() { .unwrap() ) } -#[derive(Deserialize, Serialize, Parser)] -#[serde(rename_all = "camelCase")] -#[command(rename_all = "kebab-case")] -pub struct CliLoginParams { - password: Option, -} #[instrument(skip_all)] async fn cli_login( - ctx: CliContext, - CliLoginParams { password }: CliLoginParams, + HandlerArgs { + context: ctx, + parent_method, + method, + .. + }: HandlerArgs, ) -> Result<(), RpcError> { - let password = if let Some(password) = password { - password.decrypt(&ctx)? - } else { - rpassword::prompt_password("Password: ")? - }; + let password = rpassword::prompt_password("Password: ")?; - ctx.call_remote( - "auth.login", + ctx.call_remote::( + &parent_method.into_iter().chain(method).join("."), json!({ "password": password, "metadata": { @@ -185,7 +180,8 @@ pub fn check_password_against_db(db: &DatabaseModel, password: &str) -> Result<( #[command(rename_all = "kebab-case")] pub struct LoginParams { password: Option, - #[serde(default)] + #[ts(skip)] + #[serde(rename = "__auth_userAgent")] // from Auth middleware user_agent: Option, #[serde(default)] #[ts(type = "any")] @@ -226,7 +222,8 @@ pub async fn login_impl( #[serde(rename_all = "camelCase")] #[command(rename_all = "kebab-case")] pub struct LogoutParams { - #[ts(type = "string")] + #[ts(skip)] + #[serde(rename = "__auth_session")] // from Auth middleware session: InternedString, } @@ -262,23 +259,23 @@ pub struct SessionList { sessions: Sessions, } -pub fn session() -> ParentHandler { +pub fn session() -> ParentHandler { ParentHandler::new() .subcommand( "list", from_fn_async(list) - .with_metadata("get-session", Value::Bool(true)) + .with_metadata("get_session", Value::Bool(true)) .with_display_serializable() - .with_custom_display_fn::(|handle, result| { + .with_custom_display_fn(|handle, result| { Ok(display_sessions(handle.params, result)) }) - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "kill", from_fn_async(kill) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } @@ -374,21 +371,16 @@ pub struct ResetPasswordParams { #[instrument(skip_all)] async fn cli_reset_password( - ctx: CliContext, - ResetPasswordParams { - old_password, - new_password, - }: ResetPasswordParams, + HandlerArgs { + context: ctx, + parent_method, + method, + .. + }: HandlerArgs, ) -> Result<(), RpcError> { - let old_password = if let Some(old_password) = old_password { - old_password.decrypt(&ctx)? - } else { - rpassword::prompt_password("Current Password: ")? - }; + let old_password = rpassword::prompt_password("Current Password: ")?; - let new_password = if let Some(new_password) = new_password { - new_password.decrypt(&ctx)? - } else { + let new_password = { let new_password = rpassword::prompt_password("New Password: ")?; if new_password != rpassword::prompt_password("Confirm: ")? { return Err(Error::new( @@ -400,8 +392,8 @@ async fn cli_reset_password( new_password }; - ctx.call_remote( - "auth.reset-password", + ctx.call_remote::( + &parent_method.into_iter().chain(method).join("."), imbl_value::json!({ "old-password": old_password, "new-password": new_password }), ) .await?; @@ -447,7 +439,7 @@ pub async fn reset_password_impl( #[instrument(skip_all)] pub async fn get_pubkey(ctx: RpcContext) -> Result { - let secret = ctx.as_ref().clone(); + let secret = >::as_ref(&ctx).clone(); let pub_key = secret.to_public_key()?; Ok(pub_key) } diff --git a/core/startos/src/backup/backup_bulk.rs b/core/startos/src/backup/backup_bulk.rs index 928e4811a..b72ce7b7b 100644 --- a/core/startos/src/backup/backup_bulk.rs +++ b/core/startos/src/backup/backup_bulk.rs @@ -20,7 +20,7 @@ use crate::backup::os::OsBackup; use crate::backup::{BackupReport, ServerBackupReport}; use crate::context::RpcContext; use crate::db::model::public::BackupProgress; -use crate::db::model::DatabaseModel; +use crate::db::model::{Database, DatabaseModel}; use crate::disk::mount::backup::BackupMountGuard; use crate::disk::mount::filesystem::ReadWrite; use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard}; @@ -42,9 +42,9 @@ pub struct BackupParams { password: crate::auth::PasswordType, } -struct BackupStatusGuard(Option); +struct BackupStatusGuard(Option>); impl BackupStatusGuard { - fn new(db: PatchDb) -> Self { + fn new(db: TypedPatchDb) -> Self { Self(Some(db)) } async fn handle_result( @@ -296,7 +296,7 @@ async fn perform_backup( if tokio::fs::metadata(&luks_folder_bak).await.is_ok() { tokio::fs::rename(&luks_folder_bak, &luks_folder_old).await?; } - let luks_folder = Path::new("/media/embassy/config/luks"); + let luks_folder = Path::new("/media/startos/config/luks"); if tokio::fs::metadata(&luks_folder).await.is_ok() { dir_copy(&luks_folder, &luks_folder_bak, None).await?; } diff --git a/core/startos/src/backup/mod.rs b/core/startos/src/backup/mod.rs index c963118c4..8afafaa33 100644 --- a/core/startos/src/backup/mod.rs +++ b/core/startos/src/backup/mod.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use chrono::{DateTime, Utc}; use models::{HostId, PackageId}; use reqwest::Url; -use rpc_toolkit::{from_fn_async, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use crate::context::CliContext; @@ -34,23 +34,23 @@ pub struct PackageBackupReport { } // #[command(subcommands(backup_bulk::backup_all, target::target))] -pub fn backup() -> ParentHandler { +pub fn backup() -> ParentHandler { ParentHandler::new() .subcommand( "create", from_fn_async(backup_bulk::backup_all) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) - .subcommand("target", target::target()) + .subcommand("target", target::target::()) } -pub fn package_backup() -> ParentHandler { +pub fn package_backup() -> ParentHandler { ParentHandler::new().subcommand( "restore", from_fn_async(restore::restore_packages_rpc) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } @@ -61,5 +61,5 @@ struct BackupMetadata { pub network_keys: BTreeMap>, #[serde(default)] pub tor_keys: BTreeMap>, // DEPRECATED - pub marketplace_url: Option, + pub registry: Option, } diff --git a/core/startos/src/backup/target/cifs.rs b/core/startos/src/backup/target/cifs.rs index ab2e91c0e..ad4f81aa0 100644 --- a/core/startos/src/backup/target/cifs.rs +++ b/core/startos/src/backup/target/cifs.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf}; use clap::Parser; use color_eyre::eyre::eyre; use imbl_value::InternedString; -use rpc_toolkit::{command, from_fn_async, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -46,25 +46,25 @@ pub struct CifsBackupTarget { start_os: Option, } -pub fn cifs() -> ParentHandler { +pub fn cifs() -> ParentHandler { ParentHandler::new() .subcommand( "add", from_fn_async(add) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "update", from_fn_async(update) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "remove", from_fn_async(remove) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } diff --git a/core/startos/src/backup/target/mod.rs b/core/startos/src/backup/target/mod.rs index c0f2ef10e..4d00a2501 100644 --- a/core/startos/src/backup/target/mod.rs +++ b/core/startos/src/backup/target/mod.rs @@ -8,7 +8,7 @@ use color_eyre::eyre::eyre; use digest::generic_array::GenericArray; use digest::OutputSizeUser; use models::PackageId; -use rpc_toolkit::{command, from_fn_async, AnyContext, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use sha2::Sha256; use tokio::sync::Mutex; @@ -138,23 +138,23 @@ impl FileSystem for BackupTargetFS { } // #[command(subcommands(cifs::cifs, list, info, mount, umount))] -pub fn target() -> ParentHandler { +pub fn target() -> ParentHandler { ParentHandler::new() - .subcommand("cifs", cifs::cifs()) + .subcommand("cifs", cifs::cifs::()) .subcommand( "list", from_fn_async(list) .with_display_serializable() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "info", from_fn_async(info) .with_display_serializable() - .with_custom_display_fn::(|params, info| { + .with_custom_display_fn::(|params, info| { Ok(display_backup_info(params.params, info)) }) - .with_remote_cli::(), + .with_call_remote::(), ) } diff --git a/core/startos/src/bins/mod.rs b/core/startos/src/bins/mod.rs index 68f2802e0..4a4670a5b 100644 --- a/core/startos/src/bins/mod.rs +++ b/core/startos/src/bins/mod.rs @@ -5,6 +5,8 @@ use std::path::Path; #[cfg(feature = "container-runtime")] pub mod container_cli; pub mod deprecated; +#[cfg(feature = "registry")] +pub mod registry; #[cfg(feature = "cli")] pub mod start_cli; #[cfg(feature = "daemon")] @@ -20,6 +22,8 @@ fn select_executable(name: &str) -> Option)> { "start-cli" => Some(container_cli::main), #[cfg(feature = "daemon")] "startd" => Some(startd::main), + #[cfg(feature = "registry")] + "registry" => Some(registry::main), "embassy-cli" => Some(|_| deprecated::renamed("embassy-cli", "start-cli")), "embassy-sdk" => Some(|_| deprecated::renamed("embassy-sdk", "start-sdk")), "embassyd" => Some(|_| deprecated::renamed("embassyd", "startd")), diff --git a/core/startos/src/bins/registry.rs b/core/startos/src/bins/registry.rs new file mode 100644 index 000000000..3028d1766 --- /dev/null +++ b/core/startos/src/bins/registry.rs @@ -0,0 +1,86 @@ +use std::ffi::OsString; + +use clap::Parser; +use futures::FutureExt; +use tokio::signal::unix::signal; +use tracing::instrument; + +use crate::net::web_server::WebServer; +use crate::prelude::*; +use crate::registry::context::{RegistryConfig, RegistryContext}; +use crate::util::logger::EmbassyLogger; + +#[instrument(skip_all)] +async fn inner_main(config: &RegistryConfig) -> Result<(), Error> { + let server = async { + let ctx = RegistryContext::init(config).await?; + let server = WebServer::registry(ctx.listen, ctx.clone()); + + let mut shutdown_recv = ctx.shutdown.subscribe(); + + let sig_handler_ctx = ctx; + let sig_handler = tokio::spawn(async move { + use tokio::signal::unix::SignalKind; + futures::future::select_all( + [ + SignalKind::interrupt(), + SignalKind::quit(), + SignalKind::terminate(), + ] + .iter() + .map(|s| { + async move { + signal(*s) + .unwrap_or_else(|_| panic!("register {:?} handler", s)) + .recv() + .await + } + .boxed() + }), + ) + .await; + sig_handler_ctx + .shutdown + .send(()) + .map_err(|_| ()) + .expect("send shutdown signal"); + }); + + shutdown_recv + .recv() + .await + .with_kind(crate::ErrorKind::Unknown)?; + + sig_handler.abort(); + + Ok::<_, Error>(server) + } + .await?; + server.shutdown().await; + + Ok(()) +} + +pub fn main(args: impl IntoIterator) { + EmbassyLogger::init(); + + let config = RegistryConfig::parse_from(args).load().unwrap(); + + let res = { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("failed to initialize runtime"); + rt.block_on(inner_main(&config)) + }; + + match res { + Ok(()) => (), + Err(e) => { + eprintln!("{}", e.source); + tracing::debug!("{:?}", e.source); + drop(e.source); + std::process::exit(e.kind as i32) + } + } +} diff --git a/core/startos/src/bins/start_cli.rs b/core/startos/src/bins/start_cli.rs index 374247f2e..17cc095a3 100644 --- a/core/startos/src/bins/start_cli.rs +++ b/core/startos/src/bins/start_cli.rs @@ -16,7 +16,7 @@ pub fn main(args: impl IntoIterator) { EmbassyLogger::init(); if let Err(e) = CliApp::new( |cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?), - crate::main_api(), + crate::expanded_api(), ) .run(args) { diff --git a/core/startos/src/bins/start_init.rs b/core/startos/src/bins/start_init.rs index 284748339..8e60884f1 100644 --- a/core/startos/src/bins/start_init.rs +++ b/core/startos/src/bins/start_init.rs @@ -104,7 +104,7 @@ async fn setup_or_init(config: &ServerConfig) -> Result, Error> Command::new("reboot") .invoke(crate::ErrorKind::Unknown) .await?; - } else if tokio::fs::metadata("/media/embassy/config/disk.guid") + } else if tokio::fs::metadata("/media/startos/config/disk.guid") .await .is_err() { @@ -136,7 +136,7 @@ async fn setup_or_init(config: &ServerConfig) -> Result, Error> tracing::debug!("{:?}", e); } } else { - let guid_string = tokio::fs::read_to_string("/media/embassy/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy + let guid_string = tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy .await?; let guid = guid_string.trim(); let requires_reboot = crate::disk::main::import( @@ -202,7 +202,7 @@ async fn inner_main(config: &ServerConfig) -> Result, Error> { crate::sound::BEP.play().await?; - run_script_if_exists("/media/embassy/config/preinit.sh").await; + run_script_if_exists("/media/startos/config/preinit.sh").await; let res = match setup_or_init(config).await { Err(e) => { @@ -213,12 +213,12 @@ async fn inner_main(config: &ServerConfig) -> Result, Error> { let ctx = DiagnosticContext::init( config, - if tokio::fs::metadata("/media/embassy/config/disk.guid") + if tokio::fs::metadata("/media/startos/config/disk.guid") .await .is_ok() { Some(Arc::new( - tokio::fs::read_to_string("/media/embassy/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy + tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy .await? .trim() .to_owned(), @@ -245,7 +245,7 @@ async fn inner_main(config: &ServerConfig) -> Result, Error> { Ok(s) => Ok(s), }; - run_script_if_exists("/media/embassy/config/postinit.sh").await; + run_script_if_exists("/media/startos/config/postinit.sh").await; res } diff --git a/core/startos/src/bins/startd.rs b/core/startos/src/bins/startd.rs index 3e571d6b2..f0bc428be 100644 --- a/core/startos/src/bins/startd.rs +++ b/core/startos/src/bins/startd.rs @@ -23,7 +23,7 @@ async fn inner_main(config: &ServerConfig) -> Result, Error> { let rpc_ctx = RpcContext::init( config, Arc::new( - tokio::fs::read_to_string("/media/embassy/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy + tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy .await? .trim() .to_owned(), @@ -129,12 +129,12 @@ pub fn main(args: impl IntoIterator) { crate::sound::BEETHOVEN.play().await?; let ctx = DiagnosticContext::init( &config, - if tokio::fs::metadata("/media/embassy/config/disk.guid") + if tokio::fs::metadata("/media/startos/config/disk.guid") .await .is_ok() { Some(Arc::new( - tokio::fs::read_to_string("/media/embassy/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy + tokio::fs::read_to_string("/media/startos/config/disk.guid") // unique identifier for volume group - keeps track of the disk that goes with your embassy .await? .trim() .to_owned(), diff --git a/core/startos/src/config/mod.rs b/core/startos/src/config/mod.rs index c600f590c..e61517794 100644 --- a/core/startos/src/config/mod.rs +++ b/core/startos/src/config/mod.rs @@ -9,7 +9,7 @@ use models::{ErrorKind, OptionExt, PackageId}; use patch_db::value::InternedString; use patch_db::Value; use regex::Regex; -use rpc_toolkit::{from_fn_async, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; @@ -134,16 +134,19 @@ pub struct ConfigParams { } // #[command(subcommands(get, set))] -pub fn config() -> ParentHandler { +pub fn config() -> ParentHandler { ParentHandler::new() .subcommand( "get", from_fn_async(get) .with_inherited(|ConfigParams { id }, _| id) .with_display_serializable() - .with_remote_cli::(), + .with_call_remote::(), + ) + .subcommand( + "set", + set::().with_inherited(|ConfigParams { id }, _| id), ) - .subcommand("set", set().with_inherited(|ConfigParams { id }, _| id)) } #[instrument(skip_all)] @@ -173,13 +176,13 @@ pub struct SetParams { // metadata(sync_db = true) // )] #[instrument(skip_all)] -pub fn set() -> ParentHandler { +pub fn set() -> ParentHandler { ParentHandler::new().root_handler( from_fn_async(set_impl) .with_metadata("sync_db", Value::Bool(true)) .with_inherited(|set_params, id| (id, set_params)) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } diff --git a/core/startos/src/context/cli.rs b/core/startos/src/context/cli.rs index cc2fe232b..166ab3fd7 100644 --- a/core/startos/src/context/cli.rs +++ b/core/startos/src/context/cli.rs @@ -10,7 +10,7 @@ use reqwest::Proxy; use reqwest_cookie_store::CookieStoreMutex; use rpc_toolkit::reqwest::{Client, Url}; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{call_remote_http, CallRemote, Context}; +use rpc_toolkit::{call_remote_http, CallRemote, Context, Empty}; use tokio::net::TcpStream; use tokio::runtime::Runtime; use tokio_tungstenite::{MaybeTlsStream, WebSocketStream}; @@ -18,15 +18,17 @@ use tracing::instrument; use super::setup::CURRENT_SECRET; use crate::context::config::{local_config_path, ClientConfig}; -use crate::core::rpc_continuations::RequestGuid; +use crate::context::{DiagnosticContext, InstallContext, RpcContext, SetupContext}; use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH; use crate::prelude::*; +use crate::rpc_continuations::RequestGuid; #[derive(Debug)] pub struct CliContextSeed { pub runtime: OnceCell, pub base_url: Url, pub rpc_url: Url, + pub registry_url: Option, pub client: Client, pub cookie_store: Arc, pub cookie_path: PathBuf, @@ -66,6 +68,8 @@ impl CliContext { "http://localhost".parse()? }; + let registry = config.registry.clone(); + let cookie_path = config.cookie_path.unwrap_or_else(|| { local_config_path() .as_deref() @@ -104,6 +108,17 @@ impl CliContext { .push("v1"); url }, + registry_url: registry + .map(|mut registry| { + registry + .path_segments_mut() + .map_err(|_| eyre!("Url cannot be base")) + .with_kind(crate::ErrorKind::ParseUrl)? + .push("rpc") + .push("v0"); + Ok::<_, Error>(registry) + }) + .transpose()?, client: { let mut builder = Client::builder().cookie_provider(cookie_store.clone()); if let Some(proxy) = config.proxy { @@ -198,6 +213,29 @@ impl CliContext { .await .with_kind(ErrorKind::Network) } + + pub async fn call_remote( + &self, + method: &str, + params: Value, + ) -> Result + where + Self: CallRemote, + { + >::call_remote(&self, method, params, Empty {}) + .await + } + pub async fn call_remote_with( + &self, + method: &str, + params: Value, + extra: T, + ) -> Result + where + Self: CallRemote, + { + >::call_remote(&self, method, params, extra).await + } } impl AsRef for CliContext { fn as_ref(&self) -> &Jwk { @@ -223,9 +261,23 @@ impl Context for CliContext { .clone() } } -#[async_trait::async_trait] -impl CallRemote for CliContext { - async fn call_remote(&self, method: &str, params: Value) -> Result { +impl CallRemote for CliContext { + async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { + call_remote_http(&self.client, self.rpc_url.clone(), method, params).await + } +} +impl CallRemote for CliContext { + async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { + call_remote_http(&self.client, self.rpc_url.clone(), method, params).await + } +} +impl CallRemote for CliContext { + async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { + call_remote_http(&self.client, self.rpc_url.clone(), method, params).await + } +} +impl CallRemote for CliContext { + async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { call_remote_http(&self.client, self.rpc_url.clone(), method, params).await } } diff --git a/core/startos/src/context/config.rs b/core/startos/src/context/config.rs index 0e6d9feff..bc2da00e2 100644 --- a/core/startos/src/context/config.rs +++ b/core/startos/src/context/config.rs @@ -14,7 +14,7 @@ use crate::init::init_postgres; use crate::prelude::*; use crate::util::serde::IoFormat; -pub const DEVICE_CONFIG_PATH: &str = "/media/embassy/config/config.yaml"; // "/media/startos/config/config.yaml"; +pub const DEVICE_CONFIG_PATH: &str = "/media/startos/config/config.yaml"; // "/media/startos/config/config.yaml"; pub const CONFIG_PATH: &str = "/etc/startos/config.yaml"; pub const CONFIG_PATH_LOCAL: &str = ".startos/config.yaml"; @@ -58,6 +58,8 @@ pub struct ClientConfig { pub config: Option, #[arg(short = 'h', long = "host")] pub host: Option, + #[arg(short = 'r', long = "registry")] + pub registry: Option, #[arg(short = 'p', long = "proxy")] pub proxy: Option, #[arg(long = "cookie-path")] @@ -71,8 +73,10 @@ impl ContextConfig for ClientConfig { } fn merge_with(&mut self, other: Self) { self.host = self.host.take().or(other.host); + self.registry = self.registry.take().or(other.registry); self.proxy = self.proxy.take().or(other.proxy); self.cookie_path = self.cookie_path.take().or(other.cookie_path); + self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path); } } impl ClientConfig { diff --git a/core/startos/src/context/diagnostic.rs b/core/startos/src/context/diagnostic.rs index 117e56061..10379dcf3 100644 --- a/core/startos/src/context/diagnostic.rs +++ b/core/startos/src/context/diagnostic.rs @@ -8,6 +8,7 @@ use tokio::sync::broadcast::Sender; use tracing::instrument; use crate::context::config::ServerConfig; +use crate::rpc_continuations::RpcContinuations; use crate::shutdown::Shutdown; use crate::Error; @@ -16,6 +17,7 @@ pub struct DiagnosticContextSeed { pub shutdown: Sender>, pub error: Arc, pub disk_guid: Option>, + pub rpc_continuations: RpcContinuations, } #[derive(Clone)] @@ -37,10 +39,15 @@ impl DiagnosticContext { shutdown, disk_guid, error: Arc::new(error.into()), + rpc_continuations: RpcContinuations::new(), }))) } } - +impl AsRef for DiagnosticContext { + fn as_ref(&self) -> &RpcContinuations { + &self.rpc_continuations + } +} impl Context for DiagnosticContext {} impl Deref for DiagnosticContext { type Target = DiagnosticContextSeed; diff --git a/core/startos/src/context/rpc.rs b/core/startos/src/context/rpc.rs index f2c859273..cf2d28085 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -6,33 +6,32 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; -use imbl_value::InternedString; use josekit::jwk::Jwk; -use patch_db::PatchDb; use reqwest::{Client, Proxy}; -use rpc_toolkit::Context; +use rpc_toolkit::yajrc::RpcError; +use rpc_toolkit::{CallRemote, Context, Empty}; use tokio::sync::{broadcast, oneshot, Mutex, RwLock}; use tokio::time::Instant; use tracing::instrument; use super::setup::CURRENT_SECRET; +use crate::account::AccountInfo; use crate::context::config::ServerConfig; -use crate::core::rpc_continuations::{RequestGuid, RestHandler, RpcContinuation, WebSocketHandler}; -use crate::db::prelude::PatchDbExt; +use crate::db::model::Database; use crate::dependencies::compute_dependency_config_errs; use crate::disk::OsPartitionInfo; use crate::init::check_time_is_synchronized; -use crate::lxc::{LxcContainer, LxcManager}; +use crate::lxc::{ContainerId, LxcContainer, LxcManager}; use crate::middleware::auth::HashSessionToken; use crate::net::net_controller::NetController; use crate::net::utils::{find_eth_iface, find_wifi_iface}; use crate::net::wifi::WpaCli; use crate::prelude::*; +use crate::rpc_continuations::RpcContinuations; use crate::service::ServiceMap; use crate::shutdown::Shutdown; use crate::system::get_mem_info; use crate::util::lshw::{lshw, LshwDevice}; -use crate::{account::AccountInfo, lxc::ContainerId}; pub struct RpcContextSeed { is_closed: AtomicBool, @@ -41,7 +40,7 @@ pub struct RpcContextSeed { pub ethernet_interface: String, pub datadir: PathBuf, pub disk_guid: Arc, - pub db: PatchDb, + pub db: TypedPatchDb, pub account: RwLock, pub net_controller: Arc, pub services: ServiceMap, @@ -50,7 +49,7 @@ pub struct RpcContextSeed { pub tor_socks: SocketAddr, pub lxc_manager: Arc, pub open_authed_websockets: Mutex>>>, - pub rpc_stream_continuations: Mutex>, + pub rpc_continuations: RpcContinuations, pub wifi_manager: Option>>, pub current_secret: Arc, pub client: Client, @@ -80,7 +79,7 @@ impl RpcContext { ))); let (shutdown, _) = tokio::sync::broadcast::channel(1); - let db = config.db().await?; + let db = TypedPatchDb::::load(config.db().await?).await?; let peek = db.peek().await; let account = AccountInfo::load(&peek)?; tracing::info!("Opened PatchDB"); @@ -159,7 +158,7 @@ impl RpcContext { tor_socks: tor_proxy, lxc_manager: Arc::new(LxcManager::new()), open_authed_websockets: Mutex::new(BTreeMap::new()), - rpc_stream_continuations: Mutex::new(BTreeMap::new()), + rpc_continuations: RpcContinuations::new(), wifi_manager: wifi_interface .clone() .map(|i| Arc::new(RwLock::new(WpaCli::init(i)))), @@ -236,54 +235,27 @@ impl RpcContext { Ok(()) } - - #[instrument(skip_all)] - pub async fn clean_continuations(&self) { - let mut continuations = self.rpc_stream_continuations.lock().await; - let mut to_remove = Vec::new(); - for (guid, cont) in &*continuations { - if cont.is_timed_out() { - to_remove.push(guid.clone()); - } - } - for guid in to_remove { - continuations.remove(&guid); - } - } - - #[instrument(skip_all)] - pub async fn add_continuation(&self, guid: RequestGuid, handler: RpcContinuation) { - self.clean_continuations().await; - self.rpc_stream_continuations - .lock() - .await - .insert(guid, handler); - } - - pub async fn get_ws_continuation_handler( + pub async fn call_remote( &self, - guid: &RequestGuid, - ) -> Option { - let mut continuations = self.rpc_stream_continuations.lock().await; - if !matches!(continuations.get(guid), Some(RpcContinuation::WebSocket(_))) { - return None; - } - let Some(RpcContinuation::WebSocket(x)) = continuations.remove(guid) else { - return None; - }; - x.get().await + method: &str, + params: Value, + ) -> Result + where + Self: CallRemote, + { + >::call_remote(&self, method, params, Empty {}) + .await } - - pub async fn get_rest_continuation_handler(&self, guid: &RequestGuid) -> Option { - let mut continuations: tokio::sync::MutexGuard<'_, BTreeMap> = - self.rpc_stream_continuations.lock().await; - if !matches!(continuations.get(guid), Some(RpcContinuation::Rest(_))) { - return None; - } - let Some(RpcContinuation::Rest(x)) = continuations.remove(guid) else { - return None; - }; - x.get().await + pub async fn call_remote_with( + &self, + method: &str, + params: Value, + extra: T, + ) -> Result + where + Self: CallRemote, + { + >::call_remote(&self, method, params, extra).await } } impl AsRef for RpcContext { @@ -291,6 +263,11 @@ impl AsRef for RpcContext { &CURRENT_SECRET } } +impl AsRef for RpcContext { + fn as_ref(&self) -> &RpcContinuations { + &self.rpc_continuations + } +} impl Context for RpcContext {} impl Deref for RpcContext { type Target = RpcContextSeed; diff --git a/core/startos/src/control.rs b/core/startos/src/control.rs index 3ef7d6030..d4a595a61 100644 --- a/core/startos/src/control.rs +++ b/core/startos/src/control.rs @@ -1,7 +1,6 @@ use clap::Parser; use color_eyre::eyre::eyre; use models::PackageId; -use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; diff --git a/core/startos/src/core/mod.rs b/core/startos/src/core/mod.rs deleted file mode 100644 index 7c2dbbb06..000000000 --- a/core/startos/src/core/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod rpc_continuations; diff --git a/core/startos/src/core/rpc_continuations.rs b/core/startos/src/core/rpc_continuations.rs deleted file mode 100644 index 0f25dd383..000000000 --- a/core/startos/src/core/rpc_continuations.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::time::Duration; - -use axum::extract::ws::WebSocket; -use axum::extract::Request; -use axum::response::Response; -use futures::future::BoxFuture; -use helpers::TimedResource; -use imbl_value::InternedString; - -#[allow(unused_imports)] -use crate::prelude::*; -use crate::util::new_guid; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] -pub struct RequestGuid(InternedString); -impl RequestGuid { - pub fn new() -> Self { - Self(new_guid()) - } - - pub fn from(r: &str) -> Option { - if r.len() != 32 { - return None; - } - for c in r.chars() { - if !(c >= 'A' && c <= 'Z' || c >= '2' && c <= '7') { - return None; - } - } - Some(RequestGuid(InternedString::intern(r))) - } -} -impl AsRef for RequestGuid { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} - -#[test] -fn parse_guid() { - println!( - "{:?}", - RequestGuid::from(&format!("{}", RequestGuid::new())) - ) -} - -impl std::fmt::Display for RequestGuid { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -pub type RestHandler = - Box BoxFuture<'static, Result> + Send>; - -pub type WebSocketHandler = Box BoxFuture<'static, ()> + Send>; - -pub enum RpcContinuation { - Rest(TimedResource), - WebSocket(TimedResource), -} -impl RpcContinuation { - pub fn rest(handler: RestHandler, timeout: Duration) -> Self { - RpcContinuation::Rest(TimedResource::new(handler, timeout)) - } - pub fn ws(handler: WebSocketHandler, timeout: Duration) -> Self { - RpcContinuation::WebSocket(TimedResource::new(handler, timeout)) - } - pub fn is_timed_out(&self) -> bool { - match self { - RpcContinuation::Rest(a) => a.is_timed_out(), - RpcContinuation::WebSocket(a) => a.is_timed_out(), - } - } -} diff --git a/core/startos/src/db/mod.rs b/core/startos/src/db/mod.rs index 95e42f46b..0bb8a23db 100644 --- a/core/startos/src/db/mod.rs +++ b/core/startos/src/db/mod.rs @@ -11,10 +11,11 @@ use clap::Parser; use futures::{FutureExt, StreamExt}; use http::header::COOKIE; use http::HeaderMap; +use itertools::Itertools; use patch_db::json_ptr::{JsonPointer, ROOT}; use patch_db::{Dump, Revision}; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{command, from_fn_async, CallRemote, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use serde_json::Value; use tokio::sync::oneshot; @@ -167,11 +168,11 @@ pub async fn subscribe( })) } -pub fn db() -> ParentHandler { +pub fn db() -> ParentHandler { ParentHandler::new() .subcommand("dump", from_fn_async(cli_dump).with_display_serializable()) .subcommand("dump", from_fn_async(dump).no_cli()) - .subcommand("put", put()) + .subcommand("put", put::()) .subcommand("apply", from_fn_async(cli_apply).no_display()) .subcommand("apply", from_fn_async(apply).no_cli()) } @@ -195,21 +196,28 @@ pub struct CliDumpParams { #[instrument(skip_all)] async fn cli_dump( - ctx: CliContext, - CliDumpParams { - path, - include_private, - }: CliDumpParams, + HandlerArgs { + context, + parent_method, + method, + params: CliDumpParams { + include_private, + path, + }, + .. + }: HandlerArgs, ) -> Result { let dump = if let Some(path) = path { PatchDb::open(path).await?.dump(&ROOT).await } else { + let method = parent_method.into_iter().chain(method).join("."); from_value::( - ctx.call_remote( - "db.dump", - imbl_value::json!({ "includePrivate":include_private }), - ) - .await?, + context + .call_remote::( + &method, + imbl_value::json!({ "includePrivate":include_private }), + ) + .await?, )? }; @@ -237,36 +245,54 @@ pub async fn dump( }) } +#[derive(Deserialize, Serialize, Parser)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct CliApplyParams { + expr: String, + path: Option, +} + #[instrument(skip_all)] async fn cli_apply( - ctx: CliContext, - ApplyParams { expr, path }: ApplyParams, + HandlerArgs { + context, + parent_method, + method, + params: CliApplyParams { expr, path }, + .. + }: HandlerArgs, ) -> Result<(), RpcError> { if let Some(path) = path { PatchDb::open(path) .await? - .mutate(|db| { + .apply_function(|db| { let res = apply_expr( - serde_json::to_value(patch_db::Value::from(db.clone())) + serde_json::to_value(patch_db::Value::from(db)) .with_kind(ErrorKind::Deserialization)? .into(), &expr, )?; - db.ser( - &serde_json::from_value::(res.clone().into()).with_ctx( - |_| { - ( - crate::ErrorKind::Deserialization, - "result does not match database model", - ) - }, + Ok::<_, Error>(( + to_value( + &serde_json::from_value::(res.clone().into()).with_ctx( + |_| { + ( + crate::ErrorKind::Deserialization, + "result does not match database model", + ) + }, + )?, )?, - ) + (), + )) }) .await?; } else { - ctx.call_remote("db.apply", imbl_value::json!({ "expr": expr })) + let method = parent_method.into_iter().chain(method).join("."); + context + .call_remote::(&method, imbl_value::json!({ "expr": expr })) .await?; } @@ -278,10 +304,9 @@ async fn cli_apply( #[command(rename_all = "kebab-case")] pub struct ApplyParams { expr: String, - path: Option, } -pub async fn apply(ctx: RpcContext, ApplyParams { expr, .. }: ApplyParams) -> Result<(), Error> { +pub async fn apply(ctx: RpcContext, ApplyParams { expr }: ApplyParams) -> Result<(), Error> { ctx.db .mutate(|db| { let res = apply_expr( @@ -303,12 +328,12 @@ pub async fn apply(ctx: RpcContext, ApplyParams { expr, .. }: ApplyParams) -> Re .await } -pub fn put() -> ParentHandler { +pub fn put() -> ParentHandler { ParentHandler::new().subcommand( "ui", from_fn_async(ui) .with_display_serializable() - .with_remote_cli::(), + .with_call_remote::(), ) } #[derive(Deserialize, Serialize, Parser, TS)] diff --git a/core/startos/src/db/model/package.rs b/core/startos/src/db/model/package.rs index 29b7bb90f..3fad0ad9f 100644 --- a/core/startos/src/db/model/package.rs +++ b/core/startos/src/db/model/package.rs @@ -54,7 +54,7 @@ impl PackageState { pub fn expect_installed(&self) -> Result<&InstalledState, Error> { match self { Self::Installed(a) => Ok(a), - a => Err(Error::new( + _ => Err(Error::new( eyre!( "Package {} is not in installed state", self.as_manifest(ManifestPreference::Old).id @@ -161,7 +161,7 @@ impl Model { pub fn expect_installed(&self) -> Result<&Model, Error> { match self.as_match() { PackageStateMatchModelRef::Installed(a) => Ok(a), - a => Err(Error::new( + _ => Err(Error::new( eyre!( "Package {} is not in installed state", self.as_manifest(ManifestPreference::Old).as_id().de()? @@ -251,7 +251,7 @@ impl Model { PackageStateMatchModelMut::Installed(s) | PackageStateMatchModelMut::Removing(s) => { s.as_manifest_mut() } - PackageStateMatchModelMut::Error(s) => { + PackageStateMatchModelMut::Error(_) => { return Err(Error::new( eyre!("could not determine package state to get manifest"), ErrorKind::Database, @@ -325,7 +325,7 @@ pub struct PackageDataEntry { pub state_info: PackageState, pub status: Status, #[ts(type = "string | null")] - pub marketplace_url: Option, + pub registry: Option, #[ts(type = "string")] pub developer_key: Pem, pub icon: DataUrl<'static>, diff --git a/core/startos/src/db/model/public.rs b/core/startos/src/db/model/public.rs index a51303cf4..d79948c6f 100644 --- a/core/startos/src/db/model/public.rs +++ b/core/startos/src/db/model/public.rs @@ -19,6 +19,7 @@ use crate::account::AccountInfo; use crate::db::model::package::AllPackageData; use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr}; use crate::prelude::*; +use crate::progress::FullProgress; use crate::util::cpupower::Governor; use crate::util::Version; use crate::version::{Current, VersionT}; @@ -175,24 +176,13 @@ pub struct BackupProgress { pub struct ServerStatus { pub backup_progress: Option>, pub updated: bool, - pub update_progress: Option, + pub update_progress: Option, #[serde(default)] pub shutting_down: bool, #[serde(default)] pub restarting: bool, } -#[derive(Debug, Deserialize, Serialize, HasModel, TS)] -#[serde(rename_all = "camelCase")] -#[model = "Model"] -#[ts(export)] -pub struct UpdateProgress { - #[ts(type = "number | null")] - pub size: Option, - #[ts(type = "number")] - pub downloaded: u64, -} - #[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] #[serde(rename_all = "camelCase")] #[model = "Model"] diff --git a/core/startos/src/db/prelude.rs b/core/startos/src/db/prelude.rs index 43dd59002..d40c3c17c 100644 --- a/core/startos/src/db/prelude.rs +++ b/core/startos/src/db/prelude.rs @@ -1,22 +1,16 @@ use std::collections::BTreeMap; -use std::future::Future; use std::marker::PhantomData; -use std::panic::UnwindSafe; use std::str::FromStr; use chrono::{DateTime, Utc}; pub use imbl_value::Value; -use patch_db::json_ptr::ROOT; use patch_db::value::InternedString; pub use patch_db::{HasModel, PatchDb}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use crate::db::model::DatabaseModel; use crate::prelude::*; -pub type Peeked = Model; - pub fn to_value(value: &T) -> Result where T: Serialize, @@ -31,45 +25,7 @@ where patch_db::value::from_value(value).with_kind(ErrorKind::Deserialization) } -pub trait PatchDbExt { - fn peek(&self) -> impl Future + Send; - fn mutate( - &self, - f: impl FnOnce(&mut DatabaseModel) -> Result + UnwindSafe + Send, - ) -> impl Future> + Send; - fn map_mutate( - &self, - f: impl FnOnce(DatabaseModel) -> Result + UnwindSafe + Send, - ) -> impl Future> + Send; -} -impl PatchDbExt for PatchDb { - async fn peek(&self) -> DatabaseModel { - DatabaseModel::from(self.dump(&ROOT).await.value) - } - async fn mutate( - &self, - f: impl FnOnce(&mut DatabaseModel) -> Result + UnwindSafe + Send, - ) -> Result { - Ok(self - .apply_function(|mut v| { - let model = <&mut DatabaseModel>::from(&mut v); - let res = f(model)?; - Ok::<_, Error>((v, res)) - }) - .await? - .1) - } - async fn map_mutate( - &self, - f: impl FnOnce(DatabaseModel) -> Result + UnwindSafe + Send, - ) -> Result { - Ok(DatabaseModel::from( - self.apply_function(|v| f(DatabaseModel::from(v)).map(|a| (a.into(), ()))) - .await? - .0, - )) - } -} +pub type TypedPatchDb = patch_db::TypedPatchDb; /// &mut Model <=> &mut Value #[repr(transparent)] @@ -125,7 +81,7 @@ impl Model { Ok(res) } pub fn map_mutate(&mut self, f: impl FnOnce(T) -> Result) -> Result { - let mut orig = self.de()?; + let orig = self.de()?; let res = f(orig)?; self.ser(&res)?; Ok(res) @@ -262,10 +218,9 @@ where .into()), } } - pub fn upsert(&mut self, key: &T::Key, value: F) -> Result<&mut Model, Error> + pub fn upsert(&mut self, key: &T::Key, value: F) -> Result<&mut Model, Error> where - F: FnOnce() -> D, - D: AsRef, + F: FnOnce() -> T::Value, { use serde::ser::Error; match &mut self.value { @@ -278,7 +233,7 @@ where s.as_ref().index_or_insert(v) }); if !exists { - res.ser(value().as_ref())?; + res.ser(&value())?; } Ok(res) } @@ -375,6 +330,18 @@ where } } impl Model { + pub fn contains_key(&self, key: &T::Key) -> Result { + use serde::de::Error; + let s = T::key_str(key)?; + match &self.value { + Value::Object(o) => Ok(o.contains_key(s.as_ref())), + v => Err(patch_db::value::Error { + source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")), + kind: patch_db::value::ErrorKind::Deserialization, + } + .into()), + } + } pub fn into_idx(self, key: &T::Key) -> Option> { use patch_db::ModelExt; let s = T::key_str(key).ok()?; diff --git a/core/startos/src/dependencies.rs b/core/startos/src/dependencies.rs index 42a19abe3..a746a42c9 100644 --- a/core/startos/src/dependencies.rs +++ b/core/startos/src/dependencies.rs @@ -4,7 +4,7 @@ use std::time::Duration; use clap::Parser; use models::PackageId; use patch_db::json_patch::merge; -use rpc_toolkit::{command, from_fn_async, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; @@ -15,8 +15,8 @@ use crate::db::model::package::CurrentDependencies; use crate::prelude::*; use crate::Error; -pub fn dependency() -> ParentHandler { - ParentHandler::new().subcommand("configure", configure()) +pub fn dependency() -> ParentHandler { + ParentHandler::new().subcommand("configure", configure::()) } #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)] @@ -50,7 +50,7 @@ pub struct ConfigureParams { dependent_id: PackageId, dependency_id: PackageId, } -pub fn configure() -> ParentHandler { +pub fn configure() -> ParentHandler { ParentHandler::new().root_handler( from_fn_async(configure_impl) .with_inherited(|params, _| params) diff --git a/core/startos/src/developer/mod.rs b/core/startos/src/developer/mod.rs index 596957445..79a875dfc 100644 --- a/core/startos/src/developer/mod.rs +++ b/core/startos/src/developer/mod.rs @@ -8,6 +8,7 @@ use ed25519_dalek::{SigningKey, VerifyingKey}; use tracing::instrument; use crate::context::CliContext; +use crate::util::serde::Pem; use crate::{Error, ResultExt}; #[instrument(skip_all)] @@ -45,3 +46,7 @@ pub fn init(ctx: CliContext) -> Result<(), Error> { } Ok(()) } + +pub fn pubkey(ctx: CliContext) -> Result, Error> { + Ok(Pem(ctx.developer_key()?.verifying_key())) +} diff --git a/core/startos/src/diagnostic.rs b/core/startos/src/diagnostic.rs index 1cef6d7ef..485f2359f 100644 --- a/core/startos/src/diagnostic.rs +++ b/core/startos/src/diagnostic.rs @@ -1,38 +1,48 @@ use std::path::Path; use std::sync::Arc; -use clap::Parser; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{command, from_fn, from_fn_async, AnyContext, HandlerExt, ParentHandler}; -use serde::{Deserialize, Serialize}; -use ts_rs::TS; +use rpc_toolkit::{ + from_fn, from_fn_async, CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, +}; -use crate::context::{CliContext, DiagnosticContext}; +use crate::context::{CliContext, DiagnosticContext, RpcContext}; use crate::init::SYSTEM_REBUILD_PATH; -use crate::logs::{fetch_logs, LogResponse, LogSource}; use crate::shutdown::Shutdown; use crate::Error; -pub fn diagnostic() -> ParentHandler { +pub fn diagnostic() -> ParentHandler { ParentHandler::new() - .subcommand("error", from_fn(error).with_remote_cli::()) - .subcommand("logs", from_fn_async(logs).no_cli()) + .subcommand("error", from_fn(error).with_call_remote::()) + .subcommand("logs", crate::system::logs::()) + .subcommand( + "logs", + from_fn_async(crate::logs::cli_logs::).no_display(), + ) + .subcommand( + "kernel-logs", + crate::system::kernel_logs::(), + ) + .subcommand( + "kernel-logs", + from_fn_async(crate::logs::cli_logs::).no_display(), + ) .subcommand( "exit", - from_fn(exit).no_display().with_remote_cli::(), + from_fn(exit).no_display().with_call_remote::(), ) .subcommand( "restart", from_fn(restart) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) - .subcommand("disk", disk()) + .subcommand("disk", disk::()) .subcommand( "rebuild", from_fn_async(rebuild) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } @@ -41,26 +51,6 @@ pub fn error(ctx: DiagnosticContext) -> Result, Error> { Ok(ctx.error.clone()) } -#[derive(Deserialize, Serialize, Parser, TS)] -#[serde(rename_all = "camelCase")] -#[command(rename_all = "kebab-case")] -pub struct LogsParams { - #[ts(type = "number | null")] - limit: Option, - cursor: Option, - before: bool, -} -pub async fn logs( - _: AnyContext, - LogsParams { - limit, - cursor, - before, - }: LogsParams, -) -> Result { - Ok(fetch_logs(LogSource::System, limit, cursor, before).await?) -} - pub fn exit(ctx: DiagnosticContext) -> Result<(), Error> { ctx.shutdown.send(None).expect("receiver dropped"); Ok(()) @@ -83,17 +73,20 @@ pub async fn rebuild(ctx: DiagnosticContext) -> Result<(), Error> { restart(ctx) } -pub fn disk() -> ParentHandler { - ParentHandler::new().subcommand( - "forget", - from_fn_async(forget_disk) - .no_display() - .with_remote_cli::(), - ) +pub fn disk() -> ParentHandler { + ParentHandler::new() + .subcommand("forget", from_fn_async(forget_disk::).no_cli()) + .subcommand( + "forget", + CallRemoteHandler::::new( + from_fn_async(forget_disk::).no_display(), + ) + .no_display(), + ) } -pub async fn forget_disk(_: AnyContext) -> Result<(), Error> { - let disk_guid = Path::new("/media/embassy/config/disk.guid"); +pub async fn forget_disk(_: C) -> Result<(), Error> { + let disk_guid = Path::new("/media/startos/config/disk.guid"); if tokio::fs::metadata(disk_guid).await.is_ok() { tokio::fs::remove_file(disk_guid).await?; } diff --git a/core/startos/src/disk/fsck/ext4.rs b/core/startos/src/disk/fsck/ext4.rs index 7bcbbc8b3..a068749fa 100644 --- a/core/startos/src/disk/fsck/ext4.rs +++ b/core/startos/src/disk/fsck/ext4.rs @@ -38,7 +38,7 @@ fn backup_existing_undo_file<'a>(path: &'a Path) -> BoxFuture<'a, Result<(), Err pub async fn e2fsck_aggressive( logicalname: impl AsRef + std::fmt::Debug, ) -> Result { - let undo_path = Path::new("/media/embassy/config") + let undo_path = Path::new("/media/startos/config") .join( logicalname .as_ref() diff --git a/core/startos/src/disk/main.rs b/core/startos/src/disk/main.rs index c7c634303..d414c247e 100644 --- a/core/startos/src/disk/main.rs +++ b/core/startos/src/disk/main.rs @@ -302,7 +302,7 @@ pub async fn mount_fs>( if !guid.ends_with("_UNENC") { // Backup LUKS header if e2fsck succeeded - let luks_folder = Path::new("/media/embassy/config/luks"); + let luks_folder = Path::new("/media/startos/config/luks"); tokio::fs::create_dir_all(luks_folder).await?; let tmp_luks_bak = luks_folder.join(format!(".{full_name}.luks.bak.tmp")); if tokio::fs::metadata(&tmp_luks_bak).await.is_ok() { diff --git a/core/startos/src/disk/mod.rs b/core/startos/src/disk/mod.rs index f74c944a4..68eb2b187 100644 --- a/core/startos/src/disk/mod.rs +++ b/core/startos/src/disk/mod.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; -use rpc_toolkit::{from_fn_async, AnyContext, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{ + from_fn_async, CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, +}; use serde::{Deserialize, Serialize}; use crate::context::{CliContext, RpcContext}; @@ -14,7 +16,7 @@ pub mod mount; pub mod util; pub const BOOT_RW_PATH: &str = "/media/boot-rw"; -pub const REPAIR_DISK_PATH: &str = "/media/embassy/config/repair-disk"; +pub const REPAIR_DISK_PATH: &str = "/media/startos/config/repair-disk"; #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -40,22 +42,23 @@ impl OsPartitionInfo { } } -pub fn disk() -> ParentHandler { +pub fn disk() -> ParentHandler { ParentHandler::new() .subcommand( "list", from_fn_async(list) .with_display_serializable() - .with_custom_display_fn::(|handle, result| { + .with_custom_display_fn(|handle, result| { Ok(display_disk_info(handle.params, result)) }) - .with_remote_cli::(), + .with_call_remote::(), ) + .subcommand("repair", from_fn_async(|_: C| repair()).no_cli()) .subcommand( "repair", - from_fn_async(repair) - .no_display() - .with_remote_cli::(), + CallRemoteHandler::::new( + from_fn_async(|_: RpcContext| repair()).no_display(), + ), ) } diff --git a/core/startos/src/disk/mount/filesystem/overlayfs.rs b/core/startos/src/disk/mount/filesystem/overlayfs.rs index ad5eec501..f96de7b11 100644 --- a/core/startos/src/disk/mount/filesystem/overlayfs.rs +++ b/core/startos/src/disk/mount/filesystem/overlayfs.rs @@ -11,17 +11,21 @@ use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard}; use crate::prelude::*; use crate::util::io::TmpDir; -struct OverlayFs, P1: AsRef> { +pub struct OverlayFs, P1: AsRef, P2: AsRef> { lower: P0, upper: P1, + work: P2, } -impl, P1: AsRef> OverlayFs { - pub fn new(lower: P0, upper: P1) -> Self { - Self { lower, upper } +impl, P1: AsRef, P2: AsRef> OverlayFs { + pub fn new(lower: P0, upper: P1, work: P2) -> Self { + Self { lower, upper, work } } } -impl + Send + Sync, P1: AsRef + Send + Sync> FileSystem - for OverlayFs +impl< + P0: AsRef + Send + Sync, + P1: AsRef + Send + Sync, + P2: AsRef + Send + Sync, + > FileSystem for OverlayFs { fn mount_type(&self) -> Option> { Some("overlay") @@ -33,24 +37,20 @@ impl + Send + Sync, P1: AsRef + Send + Sync> FileSystem [ Box::new(lazy_format!("lowerdir={}", self.lower.as_ref().display())) as Box, - Box::new(lazy_format!( - "upperdir={}/upper", - self.upper.as_ref().display() - )), - Box::new(lazy_format!( - "workdir={}/work", - self.upper.as_ref().display() - )), + Box::new(lazy_format!("upperdir={}", self.upper.as_ref().display())), + Box::new(lazy_format!("workdir={}", self.work.as_ref().display())), ] } async fn pre_mount(&self) -> Result<(), Error> { - tokio::fs::create_dir_all(self.upper.as_ref().join("upper")).await?; - tokio::fs::create_dir_all(self.upper.as_ref().join("work")).await?; + tokio::fs::create_dir_all(self.upper.as_ref()).await?; + tokio::fs::create_dir_all(self.work.as_ref()).await?; Ok(()) } async fn source_hash( &self, ) -> Result::OutputSize>, Error> { + tokio::fs::create_dir_all(self.upper.as_ref()).await?; + tokio::fs::create_dir_all(self.work.as_ref()).await?; let mut sha = Sha256::new(); sha.update("OverlayFs"); sha.update( @@ -77,6 +77,18 @@ impl + Send + Sync, P1: AsRef + Send + Sync> FileSystem .as_os_str() .as_bytes(), ); + sha.update( + tokio::fs::canonicalize(self.work.as_ref()) + .await + .with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + self.upper.as_ref().display().to_string(), + ) + })? + .as_os_str() + .as_bytes(), + ); Ok(sha.finalize()) } } @@ -95,7 +107,11 @@ impl OverlayGuard { let lower = TmpMountGuard::mount(base, ReadOnly).await?; let upper = TmpDir::new().await?; let inner_guard = MountGuard::mount( - &OverlayFs::new(lower.path(), upper.as_ref()), + &OverlayFs::new( + lower.path(), + upper.as_ref().join("upper"), + upper.as_ref().join("work"), + ), mountpoint, ReadWrite, ) diff --git a/core/startos/src/disk/mount/guard.rs b/core/startos/src/disk/mount/guard.rs index dc73f5527..d08b04881 100644 --- a/core/startos/src/disk/mount/guard.rs +++ b/core/startos/src/disk/mount/guard.rs @@ -2,7 +2,6 @@ use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use std::sync::{Arc, Weak}; -use bytes::Buf; use lazy_static::lazy_static; use models::ResultExt; use tokio::sync::Mutex; @@ -13,7 +12,7 @@ use super::util::unmount; use crate::util::{Invoke, Never}; use crate::Error; -pub const TMP_MOUNTPOINT: &'static str = "/media/embassy/tmp"; +pub const TMP_MOUNTPOINT: &'static str = "/media/startos/tmp"; #[async_trait::async_trait] pub trait GenericMountGuard: std::fmt::Debug + Send + Sync + 'static { diff --git a/core/startos/src/error.rs b/core/startos/src/error.rs index 9f0493f10..a0ca5707c 100644 --- a/core/startos/src/error.rs +++ b/core/startos/src/error.rs @@ -58,7 +58,7 @@ impl std::error::Error for ErrorCollection {} macro_rules! ensure_code { ($x:expr, $c:expr, $fmt:expr $(, $arg:expr)*) => { if !($x) { - return Err(crate::error::Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c)); + Err::<(), _>(crate::error::Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c))?; } }; } diff --git a/core/startos/src/init.rs b/core/startos/src/init.rs index 3f7d7f1bc..361288c10 100644 --- a/core/startos/src/init.rs +++ b/core/startos/src/init.rs @@ -12,6 +12,7 @@ use tracing::instrument; use crate::account::AccountInfo; use crate::context::config::ServerConfig; use crate::db::model::public::ServerStatus; +use crate::db::model::Database; use crate::disk::mount::util::unmount; use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH; use crate::prelude::*; @@ -20,8 +21,8 @@ use crate::util::cpupower::{get_available_governors, get_preferred_governor, set use crate::util::Invoke; use crate::{Error, ARCH}; -pub const SYSTEM_REBUILD_PATH: &str = "/media/embassy/config/system-rebuild"; -pub const STANDBY_MODE_PATH: &str = "/media/embassy/config/standby"; +pub const SYSTEM_REBUILD_PATH: &str = "/media/startos/config/system-rebuild"; +pub const STANDBY_MODE_PATH: &str = "/media/startos/config/standby"; pub async fn check_time_is_synchronized() -> Result { Ok(String::from_utf8( @@ -179,7 +180,7 @@ pub async fn init_postgres(datadir: impl AsRef) -> Result<(), Error> { } pub struct InitResult { - pub db: patch_db::PatchDb, + pub db: TypedPatchDb, } #[instrument(skip_all)] @@ -207,7 +208,7 @@ pub async fn init(cfg: &ServerConfig) -> Result { .await?; } - let db = cfg.db().await?; + let db = TypedPatchDb::::load_unchecked(cfg.db().await?); let peek = db.peek().await; tracing::info!("Opened PatchDB"); diff --git a/core/startos/src/install/mod.rs b/core/startos/src/install/mod.rs index ca8f1edbd..f5cff36d9 100644 --- a/core/startos/src/install/mod.rs +++ b/core/startos/src/install/mod.rs @@ -6,11 +6,12 @@ use clap::{value_parser, CommandFactory, FromArgMatches, Parser}; use color_eyre::eyre::eyre; use emver::VersionRange; use futures::{FutureExt, StreamExt}; +use itertools::Itertools; use patch_db::json_ptr::JsonPointer; use reqwest::header::{HeaderMap, CONTENT_LENGTH}; use reqwest::Url; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::CallRemote; +use rpc_toolkit::HandlerArgs; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use tokio::sync::oneshot; @@ -18,10 +19,10 @@ use tracing::instrument; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; -use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::db::model::package::{ManifestPreference, PackageState, PackageStateMatchModelRef}; use crate::prelude::*; use crate::progress::{FullProgress, PhasedProgressBar}; +use crate::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::s9pk::manifest::PackageId; use crate::s9pk::merkle_archive::source::http::HttpSource; use crate::s9pk::S9pk; @@ -110,7 +111,7 @@ pub struct InstallParams { id: PackageId, #[arg(short = 'm', long = "marketplace-url")] #[ts(type = "string | null")] - marketplace_url: Option, + registry: Option, #[arg(short = 'v', long = "version-spec")] version_spec: Option, #[arg(long = "version-priority")] @@ -125,7 +126,7 @@ pub async fn install( ctx: RpcContext, InstallParams { id, - marketplace_url, + registry, version_spec, version_priority, }: InstallParams, @@ -135,15 +136,14 @@ pub async fn install( Some(v) => &*v, }; let version: VersionRange = version_str.parse()?; - let marketplace_url = - marketplace_url.unwrap_or_else(|| crate::DEFAULT_MARKETPLACE.parse().unwrap()); + let registry = registry.unwrap_or_else(|| crate::DEFAULT_MARKETPLACE.parse().unwrap()); let version_priority = version_priority.unwrap_or_default(); let s9pk = S9pk::deserialize( &HttpSource::new( ctx.client.clone(), format!( "{}/package/v0/{}.s9pk?spec={}&version-priority={}", - marketplace_url, id, version, version_priority, + registry, id, version, version_priority, ) .parse()?, ) @@ -188,7 +188,7 @@ pub async fn sideload(ctx: RpcContext) -> Result { .with_kind(ErrorKind::Database)?, ) .await; - ctx.add_continuation( + ctx.rpc_continuations.add( progress.clone(), RpcContinuation::ws( Box::new(|mut ws| { @@ -203,28 +203,26 @@ pub async fn sideload(ctx: RpcContext) -> Result { })?; tokio::select! { res = async { - while let Some(rev) = sub.recv().await { - if !rev.patch.0.is_empty() { // TODO: don't send empty patches? - ws.send(Message::Text( - serde_json::to_string(&if let Some(p) = db - .peek() - .await - .as_public() - .as_package_data() - .as_idx(&id) - .and_then(|e| e.as_state_info().as_installing_info()).map(|i| i.as_progress()) - { - Ok::<_, ()>(p.de()?) - } else { - let mut p = FullProgress::new(); - p.overall.complete(); - Ok(p) - }) - .with_kind(ErrorKind::Serialization)?, - )) - .await - .with_kind(ErrorKind::Network)?; - } + while let Some(_) = sub.recv().await { + ws.send(Message::Text( + serde_json::to_string(&if let Some(p) = db + .peek() + .await + .as_public() + .as_package_data() + .as_idx(&id) + .and_then(|e| e.as_state_info().as_installing_info()).map(|i| i.as_progress()) + { + Ok::<_, ()>(p.de()?) + } else { + let mut p = FullProgress::new(); + p.overall.complete(); + Ok(p) + }) + .with_kind(ErrorKind::Serialization)?, + )) + .await + .with_kind(ErrorKind::Network)?; } Ok::<_, Error>(()) } => res?, @@ -322,15 +320,30 @@ impl FromArgMatches for CliInstallParams { } #[instrument(skip_all)] -pub async fn cli_install(ctx: CliContext, params: CliInstallParams) -> Result<(), RpcError> { +pub async fn cli_install( + HandlerArgs { + context: ctx, + parent_method, + method, + params, + .. + }: HandlerArgs, +) -> Result<(), RpcError> { + let method = parent_method.into_iter().chain(method).collect_vec(); match params { CliInstallParams::Sideload(path) => { let file = crate::s9pk::load(&ctx, path).await?; // rpc call remote sideload let SideloadResponse { upload, progress } = from_value::( - ctx.call_remote("package.sideload", imbl_value::json!({})) - .await?, + ctx.call_remote::( + &method[..method.len() - 1] + .into_iter() + .chain(std::iter::once(&"sideload")) + .join("."), + imbl_value::json!({}), + ) + .await?, )?; let upload = async { @@ -387,7 +400,7 @@ pub async fn cli_install(ctx: CliContext, params: CliInstallParams) -> Result<() upload?; } CliInstallParams::Marketplace(params) => { - ctx.call_remote("package.install", to_value(¶ms)?) + ctx.call_remote::(&method.join("."), to_value(¶ms)?) .await?; } } diff --git a/core/startos/src/lib.rs b/core/startos/src/lib.rs index d6dad2c87..3152fcab3 100644 --- a/core/startos/src/lib.rs +++ b/core/startos/src/lib.rs @@ -28,7 +28,6 @@ pub mod bins; pub mod config; pub mod context; pub mod control; -pub mod core; pub mod db; pub mod dependencies; pub mod developer; @@ -49,6 +48,7 @@ pub mod prelude; pub mod progress; pub mod properties; pub mod registry; +pub mod rpc_continuations; pub mod s9pk; pub mod service; pub mod setup; @@ -71,12 +71,14 @@ pub use error::{Error, ErrorKind, ResultExt}; use imbl_value::Value; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{ - command, from_fn, from_fn_async, from_fn_blocking, AnyContext, HandlerExt, ParentHandler, + from_fn, from_fn_async, from_fn_blocking, CallRemoteHandler, Context, Empty, HandlerExt, + ParentHandler, }; use serde::{Deserialize, Serialize}; use ts_rs::TS; -use crate::context::CliContext; +use crate::context::{CliContext, DiagnosticContext, InstallContext, RpcContext, SetupContext}; +use crate::registry::context::{RegistryContext, RegistryUrlParams}; use crate::util::serde::HandlerExtSerde; #[derive(Deserialize, Serialize, Parser, TS)] @@ -86,102 +88,117 @@ pub struct EchoParams { message: String, } -pub fn echo(_: AnyContext, EchoParams { message }: EchoParams) -> Result { +pub fn echo(_: C, EchoParams { message }: EchoParams) -> Result { Ok(message) } -pub fn main_api() -> ParentHandler { +pub fn main_api() -> ParentHandler { ParentHandler::new() - .subcommand("git-info", from_fn(version::git_info)) + .subcommand::("git-info", from_fn(version::git_info)) .subcommand( "echo", - from_fn(echo) + from_fn(echo::) .with_metadata("authenticated", Value::Bool(false)) - .with_remote_cli::(), + .with_call_remote::(), ) - .subcommand("init", from_fn_blocking(developer::init).no_display()) - .subcommand("server", server()) - .subcommand("package", package()) - .subcommand("net", net::net()) - .subcommand("auth", auth::auth()) - .subcommand("db", db::db()) - .subcommand("ssh", ssh::ssh()) - .subcommand("wifi", net::wifi::wifi()) - .subcommand("disk", disk::disk()) - .subcommand("notification", notifications::notification()) - .subcommand("backup", backup::backup()) - .subcommand("marketplace", registry::marketplace::marketplace()) - .subcommand("lxc", lxc::lxc()) + .subcommand("server", server::()) + .subcommand("package", package::()) + .subcommand("net", net::net::()) + .subcommand("auth", auth::auth::()) + .subcommand("db", db::db::()) + .subcommand("ssh", ssh::ssh::()) + .subcommand("wifi", net::wifi::wifi::()) + .subcommand("disk", disk::disk::()) + .subcommand("notification", notifications::notification::()) + .subcommand("backup", backup::backup::()) + .subcommand( + "registry", + CallRemoteHandler::::new( + registry::registry_api::(), + ) + .no_cli(), + ) + .subcommand("lxc", lxc::lxc::()) .subcommand("s9pk", s9pk::rpc::s9pk()) + .subcommand("util", util::rpc::util::()) } -pub fn server() -> ParentHandler { +pub fn server() -> ParentHandler { ParentHandler::new() .subcommand( "time", from_fn_async(system::time) .with_display_serializable() - .with_custom_display_fn::(|handle, result| { + .with_custom_display_fn(|handle, result| { Ok(system::display_time(handle.params, result)) }) - .with_remote_cli::(), + .with_call_remote::(), + ) + .subcommand("experimental", system::experimental::()) + .subcommand("logs", system::logs::()) + .subcommand( + "logs", + from_fn_async(logs::cli_logs::).no_display(), + ) + .subcommand("kernel-logs", system::kernel_logs::()) + .subcommand( + "kernel-logs", + from_fn_async(logs::cli_logs::).no_display(), ) - .subcommand("experimental", system::experimental()) - .subcommand("logs", system::logs()) - .subcommand("kernel-logs", system::kernel_logs()) .subcommand( "metrics", from_fn_async(system::metrics) .with_display_serializable() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "shutdown", from_fn_async(shutdown::shutdown) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "restart", from_fn_async(shutdown::restart) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "rebuild", from_fn_async(shutdown::rebuild) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "update", from_fn_async(update::update_system) .with_metadata("sync_db", Value::Bool(true)) - .with_custom_display_fn::(|handle, result| { - Ok(update::display_update_result(handle.params, result)) - }) - .with_remote_cli::(), + .no_cli(), + ) + .subcommand( + "update", + from_fn_async(update::cli_update_system).no_display(), ) .subcommand( "update-firmware", - from_fn_async(firmware::update_firmware) - .with_custom_display_fn::(|_handle, result| { + from_fn_async(|_: RpcContext| firmware::update_firmware()) + .with_custom_display_fn(|_handle, result| { Ok(firmware::display_firmware_update_result(result)) }) - .with_remote_cli::(), + .with_call_remote::(), ) } -pub fn package() -> ParentHandler { +pub fn package() -> ParentHandler { ParentHandler::new() .subcommand( "action", from_fn_async(action::action) .with_display_serializable() - .with_custom_display_fn::(|handle, result| { + .with_custom_display_fn(|handle, result| { Ok(action::display_action_result(handle.params, result)) }) - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "install", @@ -196,47 +213,51 @@ pub fn package() -> ParentHandler { from_fn_async(install::uninstall) .with_metadata("sync_db", Value::Bool(true)) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "list", from_fn_async(install::list) .with_display_serializable() - .with_remote_cli::(), + .with_call_remote::(), ) - .subcommand("config", config::config()) + .subcommand("config", config::config::()) .subcommand( "start", from_fn_async(control::start) .with_metadata("sync_db", Value::Bool(true)) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "stop", from_fn_async(control::stop) .with_metadata("sync_db", Value::Bool(true)) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "restart", from_fn_async(control::restart) .with_metadata("sync_db", Value::Bool(true)) .no_display() - .with_remote_cli::(), + .with_call_remote::(), + ) + .subcommand("logs", logs::package_logs()) + .subcommand( + "logs", + from_fn_async(logs::cli_logs::).no_display(), ) - .subcommand("logs", logs::logs()) .subcommand( "properties", from_fn_async(properties::properties) - .with_custom_display_fn::(|_handle, result| { + .with_custom_display_fn(|_handle, result| { Ok(properties::display_properties(result)) }) - .with_remote_cli::(), + .with_call_remote::(), ) - .subcommand("dependency", dependencies::dependency()) - .subcommand("backup", backup::package_backup()) + .subcommand("dependency", dependencies::dependency::()) + .subcommand("backup", backup::package_backup::()) .subcommand("connect", from_fn_async(service::connect_rpc).no_cli()) .subcommand( "connect", @@ -244,32 +265,51 @@ pub fn package() -> ParentHandler { ) } -pub fn diagnostic_api() -> ParentHandler { +pub fn diagnostic_api() -> ParentHandler { ParentHandler::new() - .subcommand( + .subcommand::( "git-info", from_fn(version::git_info).with_metadata("authenticated", Value::Bool(false)), ) - .subcommand("echo", from_fn(echo).with_remote_cli::()) - .subcommand("diagnostic", diagnostic::diagnostic()) + .subcommand( + "echo", + from_fn(echo::).with_call_remote::(), + ) + .subcommand("diagnostic", diagnostic::diagnostic::()) } -pub fn setup_api() -> ParentHandler { +pub fn setup_api() -> ParentHandler { ParentHandler::new() - .subcommand( + .subcommand::( "git-info", from_fn(version::git_info).with_metadata("authenticated", Value::Bool(false)), ) - .subcommand("echo", from_fn(echo).with_remote_cli::()) - .subcommand("setup", setup::setup()) + .subcommand( + "echo", + from_fn(echo::).with_call_remote::(), + ) + .subcommand("setup", setup::setup::()) } -pub fn install_api() -> ParentHandler { +pub fn install_api() -> ParentHandler { ParentHandler::new() - .subcommand( + .subcommand::( "git-info", from_fn(version::git_info).with_metadata("authenticated", Value::Bool(false)), ) - .subcommand("echo", from_fn(echo).with_remote_cli::()) - .subcommand("install", os_install::install()) + .subcommand( + "echo", + from_fn(echo::).with_call_remote::(), + ) + .subcommand("install", os_install::install::()) +} + +pub fn expanded_api() -> ParentHandler { + main_api() + .subcommand("init", from_fn_blocking(developer::init).no_display()) + .subcommand("pubkey", from_fn_blocking(developer::pubkey)) + .subcommand("diagnostic", diagnostic::diagnostic::()) + .subcommand("setup", setup::setup::()) + .subcommand("install", os_install::install::()) + .subcommand("registry", registry::registry_api::()) } diff --git a/core/startos/src/logs.rs b/core/startos/src/logs.rs index d8b7c898f..340b04b8b 100644 --- a/core/startos/src/logs.rs +++ b/core/startos/src/logs.rs @@ -4,13 +4,17 @@ use std::time::{Duration, UNIX_EPOCH}; use axum::extract::ws::{self, WebSocket}; use chrono::{DateTime, Utc}; -use clap::Parser; +use clap::{Args, FromArgMatches, Parser}; use color_eyre::eyre::eyre; use futures::stream::BoxStream; -use futures::{FutureExt, Stream, StreamExt, TryStreamExt}; +use futures::{Future, FutureExt, Stream, StreamExt, TryStreamExt}; +use itertools::Itertools; use models::PackageId; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{command, from_fn_async, CallRemote, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{ + from_fn_async, CallRemote, Context, Empty, HandlerArgs, HandlerExt, HandlerFor, ParentHandler, +}; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::{Child, Command}; @@ -19,10 +23,10 @@ use tokio_tungstenite::tungstenite::Message; use tracing::instrument; use crate::context::{CliContext, RpcContext}; -use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::error::ResultExt; use crate::lxc::ContainerId; use crate::prelude::*; +use crate::rpc_continuations::{RequestGuid, RpcContinuation, RpcContinuations}; use crate::util::serde::Reversible; use crate::util::Invoke; @@ -211,7 +215,6 @@ fn deserialize_log_message<'de, D: serde::de::Deserializer<'de>>( pub enum LogSource { Kernel, Unit(&'static str), - System, Container(ContainerId), } @@ -220,168 +223,195 @@ pub const SYSTEM_UNIT: &str = "startd"; #[derive(Deserialize, Serialize, Parser)] #[serde(rename_all = "camelCase")] #[command(rename_all = "kebab-case")] -pub struct LogsParam { +pub struct PackageIdParams { id: PackageId, +} + +#[derive(Deserialize, Serialize, Parser)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct LogsParams { + #[command(flatten)] + #[serde(flatten)] + extra: Extra, #[arg(short = 'l', long = "limit")] limit: Option, - #[arg(short = 'c', long = "cursor")] + #[arg(short = 'c', long = "cursor", conflicts_with = "follow")] cursor: Option, - #[arg(short = 'B', long = "before")] + #[arg(short = 'B', long = "before", conflicts_with = "follow")] #[serde(default)] before: bool, +} + +#[derive(Deserialize, Serialize, Parser)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct CliLogsParams { + #[command(flatten)] + #[serde(flatten)] + rpc_params: LogsParams, #[arg(short = 'f', long = "follow")] #[serde(default)] follow: bool, } -pub fn logs() -> ParentHandler { - ParentHandler::::new() +#[allow(private_bounds)] +pub fn logs< + C: Context + AsRef, + Extra: FromArgMatches + Serialize + DeserializeOwned + Args + Send + Sync + 'static, +>( + source: impl for<'a> LogSourceFn<'a, C, Extra>, +) -> ParentHandler> { + ParentHandler::new() .root_handler( - from_fn_async(cli_logs) - .no_display() - .with_inherited(|params, _| params), - ) - .root_handler( - from_fn_async(logs_nofollow) + logs_nofollow::(source.clone()) .with_inherited(|params, _| params) .no_cli(), ) .subcommand( "follow", - from_fn_async(logs_follow) + logs_follow::(source) .with_inherited(|params, _| params) .no_cli(), ) } -pub async fn cli_logs( - ctx: CliContext, - _: Empty, - LogsParam { - id, - limit, - cursor, - before, - follow, - }: LogsParam, -) -> Result<(), RpcError> { - if follow { - if cursor.is_some() { - return Err(RpcError::from(Error::new( - eyre!("The argument '--cursor ' cannot be used with '--follow'"), - crate::ErrorKind::InvalidRequest, - ))); - } - if before { - return Err(RpcError::from(Error::new( - eyre!("The argument '--before' cannot be used with '--follow'"), - crate::ErrorKind::InvalidRequest, - ))); - } - cli_logs_generic_follow(ctx, "package.logs.follow", Some(id), limit).await - } else { - cli_logs_generic_nofollow(ctx, "package.logs", Some(id), limit, cursor, before).await - } -} -pub async fn logs_nofollow( - ctx: RpcContext, - _: Empty, - LogsParam { - id, - limit, - cursor, - before, + +pub async fn cli_logs( + HandlerArgs { + context: ctx, + parent_method, + method, + params: CliLogsParams { rpc_params, follow }, .. - }: LogsParam, -) -> Result { - let container_id = ctx - .services - .get(&id) - .await - .as_ref() - .map(|x| x.container_id()) - .ok_or_else(|| { - Error::new( - eyre!("No service found with id: {}", id), - ErrorKind::NotFound, - ) - })??; - fetch_logs(LogSource::Container(container_id), limit, cursor, before).await -} -pub async fn logs_follow( - ctx: RpcContext, - _: Empty, - LogsParam { id, limit, .. }: LogsParam, -) -> Result { - let container_id = ctx - .services - .get(&id) - .await - .as_ref() - .map(|x| x.container_id()) - .ok_or_else(|| { - Error::new( - eyre!("No service found with id: {}", id), - ErrorKind::NotFound, - ) - })??; - follow_logs(ctx, LogSource::Container(container_id), limit).await -} + }: HandlerArgs>, +) -> Result<(), RpcError> +where + CliContext: CallRemote, + Extra: FromArgMatches + Args + Serialize + Send + Sync, +{ + let method = parent_method + .into_iter() + .chain(method) + .chain(follow.then_some("follow")) + .join("."); -pub async fn cli_logs_generic_nofollow( - ctx: CliContext, - method: &str, - id: Option, - limit: Option, - cursor: Option, - before: bool, -) -> Result<(), RpcError> { - let res = from_value::( - ctx.call_remote( - method, - imbl_value::json!({ - "id": id, - "limit": limit, - "cursor": cursor, - "before": before, - }), - ) - .await?, - )?; + if follow { + let res = from_value::( + ctx.call_remote::(&method, to_value(&rpc_params)?) + .await?, + )?; - for entry in res.entries.iter() { - println!("{}", entry); - } + let mut stream = ctx.ws_continuation(res.guid).await?; + while let Some(log) = stream.try_next().await? { + if let Message::Text(log) = log { + println!("{}", serde_json::from_str::(&log)?); + } + } + } else { + let res = from_value::( + ctx.call_remote::(&method, to_value(&rpc_params)?) + .await?, + )?; - Ok(()) -} - -pub async fn cli_logs_generic_follow( - ctx: CliContext, - method: &str, - id: Option, - limit: Option, -) -> Result<(), RpcError> { - let res = from_value::( - ctx.call_remote( - method, - imbl_value::json!({ - "id": id, - "limit": limit, - }), - ) - .await?, - )?; - - let mut stream = ctx.ws_continuation(res.guid).await?; - while let Some(log) = stream.try_next().await? { - if let Message::Text(log) = log { - println!("{}", serde_json::from_str::(&log)?); + for entry in res.entries.iter() { + println!("{}", entry); } } Ok(()) } +trait LogSourceFn<'a, Context, Extra>: Clone + Send + Sync + 'static { + type Fut: Future> + Send + 'a; + fn call(&self, ctx: &'a Context, extra: Extra) -> Self::Fut; +} + +impl<'a, C: Context, Extra, F, Fut> LogSourceFn<'a, C, Extra> for F +where + F: Fn(&'a C, Extra) -> Fut + Clone + Send + Sync + 'static, + Fut: Future> + Send + 'a, +{ + type Fut = Fut; + fn call(&self, ctx: &'a C, extra: Extra) -> Self::Fut { + self(ctx, extra) + } +} + +fn logs_nofollow( + f: impl for<'a> LogSourceFn<'a, C, Extra>, +) -> impl HandlerFor, Ok = LogResponse, Err = Error> +where + C: Context, + Extra: FromArgMatches + Args + Send + Sync + 'static, +{ + from_fn_async( + move |HandlerArgs { + context, + inherited_params: + LogsParams { + extra, + limit, + cursor, + before, + }, + .. + }: HandlerArgs>| { + let f = f.clone(); + async move { fetch_logs(f.call(&context, extra).await?, limit, cursor, before).await } + }, + ) +} + +fn logs_follow< + C: Context + AsRef, + Extra: FromArgMatches + Args + Send + Sync + 'static, +>( + f: impl for<'a> LogSourceFn<'a, C, Extra>, +) -> impl HandlerFor< + C, + Params = Empty, + InheritedParams = LogsParams, + Ok = LogFollowResponse, + Err = Error, +> { + from_fn_async( + move |HandlerArgs { + context, + inherited_params: LogsParams { extra, limit, .. }, + .. + }: HandlerArgs>| { + let f = f.clone(); + async move { + let src = f.call(&context, extra).await?; + follow_logs(context, src, limit).await + } + }, + ) +} + +async fn get_package_id( + ctx: &RpcContext, + PackageIdParams { id }: PackageIdParams, +) -> Result { + let container_id = ctx + .services + .get(&id) + .await + .as_ref() + .map(|x| x.container_id()) + .ok_or_else(|| { + Error::new( + eyre!("No service found with id: {}", id), + ErrorKind::NotFound, + ) + })??; + Ok(LogSource::Container(container_id)) +} + +pub fn package_logs() -> ParentHandler> { + logs::(get_package_id) +} + pub async fn journalctl( id: LogSource, limit: usize, @@ -474,10 +504,6 @@ fn gen_journalctl_command(id: &LogSource) -> Command { cmd.arg("-u"); cmd.arg(id); } - LogSource::System => { - cmd.arg("-u"); - cmd.arg(SYSTEM_UNIT); - } LogSource::Container(_container_id) => { cmd.arg("-u").arg("container-runtime.service"); } @@ -533,8 +559,8 @@ pub async fn fetch_logs( } #[instrument(skip_all)] -pub async fn follow_logs( - ctx: RpcContext, +pub async fn follow_logs>( + ctx: Context, id: LogSource, limit: Option, ) -> Result { @@ -556,23 +582,24 @@ pub async fn follow_logs( } let guid = RequestGuid::new(); - ctx.add_continuation( - guid.clone(), - RpcContinuation::ws( - Box::new(move |socket| { - ws_handler(first_entry, stream, socket) - .map(|x| match x { - Ok(_) => (), - Err(e) => { - tracing::error!("Error in log stream: {}", e); - } - }) - .boxed() - }), - Duration::from_secs(30), - ), - ) - .await; + ctx.as_ref() + .add( + guid.clone(), + RpcContinuation::ws( + Box::new(move |socket| { + ws_handler(first_entry, stream, socket) + .map(|x| match x { + Ok(_) => (), + Err(e) => { + tracing::error!("Error in log stream: {}", e); + } + }) + .boxed() + }), + Duration::from_secs(30), + ), + ) + .await; Ok(LogFollowResponse { start_cursor, guid }) } diff --git a/core/startos/src/lxc/mod.rs b/core/startos/src/lxc/mod.rs index 9dfdff7f9..084364c4a 100644 --- a/core/startos/src/lxc/mod.rs +++ b/core/startos/src/lxc/mod.rs @@ -12,8 +12,8 @@ use imbl_value::{InOMap, InternedString}; use models::InvalidId; use rpc_toolkit::yajrc::{RpcError, RpcResponse}; use rpc_toolkit::{ - from_fn_async, AnyContext, CallRemoteHandler, GenericRpcMethod, Handler, HandlerArgs, - HandlerExt, ParentHandler, RpcRequest, + from_fn_async, CallRemoteHandler, Context, Empty, GenericRpcMethod, HandlerArgs, HandlerExt, + HandlerFor, ParentHandler, RpcRequest, }; use rustyline_async::{ReadlineEvent, SharedWriter}; use serde::{Deserialize, Serialize}; @@ -25,7 +25,6 @@ use tokio::time::Instant; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; -use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::disk::mount::filesystem::bind::Bind; use crate::disk::mount::filesystem::block_dev::BlockDev; use crate::disk::mount::filesystem::idmapped::IdMapped; @@ -34,6 +33,7 @@ use crate::disk::mount::filesystem::{MountType, ReadWrite}; use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard}; use crate::disk::mount::util::unmount; use crate::prelude::*; +use crate::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::util::clap::FromStrParser; use crate::util::rpc_client::UnixRpcClient; use crate::util::{new_guid, Invoke}; @@ -370,16 +370,16 @@ impl Drop for LxcContainer { #[derive(Default, Serialize)] pub struct LxcConfig {} -pub fn lxc() -> ParentHandler { +pub fn lxc() -> ParentHandler { ParentHandler::new() .subcommand( "create", - from_fn_async(create).with_remote_cli::(), + from_fn_async(create).with_call_remote::(), ) .subcommand( "list", from_fn_async(list) - .with_custom_display_fn::(|_, res| { + .with_custom_display_fn(|_, res| { use prettytable::*; let mut table = table!([bc => "GUID"]); for guid in res { @@ -388,13 +388,13 @@ pub fn lxc() -> ParentHandler { table.printstd(); Ok(()) }) - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "remove", from_fn_async(remove) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand("connect", from_fn_async(connect_rpc).no_cli()) .subcommand("connect", from_fn_async(connect_rpc_cli).no_display()) @@ -448,59 +448,59 @@ pub async fn connect(ctx: &RpcContext, container: &LxcContainer) -> Result break, - Some(Ok(Message::Text(txt))) => { - let mut id = None; - let result = async { - let req: RpcRequest = - serde_json::from_str(&txt).map_err(|e| RpcError { - data: Some(serde_json::Value::String( - e.to_string(), - )), - ..rpc_toolkit::yajrc::PARSE_ERROR - })?; - id = req.id; - rpc.request(req.method, req.params).await + ctx.rpc_continuations + .add( + guid.clone(), + RpcContinuation::ws( + Box::new(|mut ws| { + async move { + if let Err(e) = async { + loop { + match ws.next().await { + None => break, + Some(Ok(Message::Text(txt))) => { + let mut id = None; + let result = async { + let req: RpcRequest = serde_json::from_str(&txt) + .map_err(|e| RpcError { + data: Some(serde_json::Value::String( + e.to_string(), + )), + ..rpc_toolkit::yajrc::PARSE_ERROR + })?; + id = req.id; + rpc.request(req.method, req.params).await + } + .await; + ws.send(Message::Text( + serde_json::to_string( + &RpcResponse:: { id, result }, + ) + .with_kind(ErrorKind::Serialization)?, + )) + .await + .with_kind(ErrorKind::Network)?; + } + Some(Ok(_)) => (), + Some(Err(e)) => { + return Err(Error::new(e, ErrorKind::Network)); } - .await; - ws.send(Message::Text( - serde_json::to_string(&RpcResponse:: { - id, - result, - }) - .with_kind(ErrorKind::Serialization)?, - )) - .await - .with_kind(ErrorKind::Network)?; - } - Some(Ok(_)) => (), - Some(Err(e)) => { - return Err(Error::new(e, ErrorKind::Network)); } } + Ok::<_, Error>(()) + } + .await + { + tracing::error!("{e}"); + tracing::debug!("{e:?}"); } - Ok::<_, Error>(()) } - .await - { - tracing::error!("{e}"); - tracing::debug!("{e:?}"); - } - } - .boxed() - }), - Duration::from_secs(30), - ), - ) - .await; + .boxed() + }), + Duration::from_secs(30), + ), + ) + .await; Ok(guid) } @@ -614,11 +614,25 @@ pub async fn connect_cli(ctx: &CliContext, guid: RequestGuid) -> Result<(), Erro } pub async fn connect_rpc_cli( - handle_args: HandlerArgs, + HandlerArgs { + context, + parent_method, + method, + params, + inherited_params, + raw_params, + }: HandlerArgs, ) -> Result<(), Error> { - let ctx = handle_args.context.clone(); - let guid = CallRemoteHandler::::new(from_fn_async(connect_rpc)) - .handle_async(handle_args) + let ctx = context.clone(); + let guid = CallRemoteHandler::::new(from_fn_async(connect_rpc)) + .handle_async(HandlerArgs { + context, + parent_method, + method, + params: rpc_toolkit::util::Flat(params, Empty {}), + inherited_params, + raw_params, + }) .await?; connect_cli(&ctx, guid).await diff --git a/core/startos/src/middleware/auth.rs b/core/startos/src/middleware/auth.rs index 30ae56744..1852c4890 100644 --- a/core/startos/src/middleware/auth.rs +++ b/core/startos/src/middleware/auth.rs @@ -245,7 +245,6 @@ impl Borrow for HashSessionToken { } #[derive(Deserialize)] -#[serde(rename_all = "camelCase")] pub struct Metadata { #[serde(default = "const_true")] authenticated: bool, @@ -274,7 +273,6 @@ impl Auth { } } } -#[async_trait::async_trait] impl Middleware for Auth { type Metadata = Metadata; async fn process_http_request( @@ -306,7 +304,7 @@ impl Middleware for Auth { }); } if let Some(user_agent) = self.user_agent.as_ref().and_then(|h| h.to_str().ok()) { - request.params["user-agent"] = Value::String(Arc::new(user_agent.to_owned())) + request.params["__auth_userAgent"] = Value::String(Arc::new(user_agent.to_owned())) // TODO: will this panic? } } else if metadata.authenticated { @@ -318,7 +316,7 @@ impl Middleware for Auth { }) } Ok(HasValidSession(SessionType::Session(s))) if metadata.get_session => { - request.params["session"] = + request.params["__auth_session"] = Value::String(Arc::new(s.hashed().deref().to_owned())); // TODO: will this panic? } diff --git a/core/startos/src/middleware/cors.rs b/core/startos/src/middleware/cors.rs index 60a472cdd..a8c406a8a 100644 --- a/core/startos/src/middleware/cors.rs +++ b/core/startos/src/middleware/cors.rs @@ -44,8 +44,7 @@ impl Cors { } } } -#[async_trait::async_trait] -impl Middleware for Cors { +impl Middleware for Cors { type Metadata = Empty; async fn process_http_request( &mut self, diff --git a/core/startos/src/middleware/db.rs b/core/startos/src/middleware/db.rs index b8cdaa231..dca0418e5 100644 --- a/core/startos/src/middleware/db.rs +++ b/core/startos/src/middleware/db.rs @@ -23,7 +23,6 @@ impl SyncDb { } } -#[async_trait::async_trait] impl Middleware for SyncDb { type Metadata = Metadata; async fn process_rpc_request( diff --git a/core/startos/src/middleware/diagnostic.rs b/core/startos/src/middleware/diagnostic.rs index f779d632f..2a7467e34 100644 --- a/core/startos/src/middleware/diagnostic.rs +++ b/core/startos/src/middleware/diagnostic.rs @@ -14,7 +14,6 @@ impl DiagnosticMode { } } -#[async_trait::async_trait] impl Middleware for DiagnosticMode { type Metadata = Empty; async fn process_rpc_request( diff --git a/core/startos/src/net/dhcp.rs b/core/startos/src/net/dhcp.rs index b0d538e81..104cda961 100644 --- a/core/startos/src/net/dhcp.rs +++ b/core/startos/src/net/dhcp.rs @@ -3,7 +3,7 @@ use std::net::IpAddr; use clap::Parser; use futures::TryStreamExt; -use rpc_toolkit::{from_fn_async, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::sync::RwLock; use ts_rs::TS; @@ -53,12 +53,12 @@ pub async fn init_ips() -> Result, Error> { } // #[command(subcommands(update))] -pub fn dhcp() -> ParentHandler { +pub fn dhcp() -> ParentHandler { ParentHandler::new().subcommand( "update", from_fn_async::<_, _, (), Error, (RpcContext, UpdateParams)>(update) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } #[derive(Deserialize, Serialize, Parser, TS)] diff --git a/core/startos/src/net/mod.rs b/core/startos/src/net/mod.rs index aaf019e66..e55da4206 100644 --- a/core/startos/src/net/mod.rs +++ b/core/startos/src/net/mod.rs @@ -1,4 +1,4 @@ -use rpc_toolkit::ParentHandler; +use rpc_toolkit::{Context, ParentHandler}; pub mod dhcp; pub mod dns; @@ -18,8 +18,8 @@ pub mod wifi; pub const PACKAGE_CERT_PATH: &str = "/var/lib/embassy/ssl"; -pub fn net() -> ParentHandler { +pub fn net() -> ParentHandler { ParentHandler::new() - .subcommand("tor", tor::tor()) - .subcommand("dhcp", dhcp::dhcp()) + .subcommand("tor", tor::tor::()) + .subcommand("dhcp", dhcp::dhcp::()) } diff --git a/core/startos/src/net/net_controller.rs b/core/startos/src/net/net_controller.rs index 97f38567d..aae98ae96 100644 --- a/core/startos/src/net/net_controller.rs +++ b/core/startos/src/net/net_controller.rs @@ -6,12 +6,10 @@ use color_eyre::eyre::eyre; use imbl::OrdMap; use lazy_format::lazy_format; use models::{HostId, OptionExt, PackageId}; -use patch_db::PatchDb; -use tokio::sync::Mutex; use torut::onion::{OnionAddressV3, TorSecretKeyV3}; use tracing::instrument; -use crate::db::prelude::PatchDbExt; +use crate::db::model::Database; use crate::error::ErrorCollection; use crate::hostname::Hostname; use crate::net::dns::DnsController; @@ -21,11 +19,12 @@ use crate::net::host::binding::{AddSslOptions, BindOptions}; use crate::net::host::{Host, HostKind}; use crate::net::tor::TorController; use crate::net::vhost::{AlpnInfo, VHostController}; +use crate::prelude::*; use crate::util::serde::MaybeUtf8String; -use crate::{Error, HOST_IP}; +use crate::HOST_IP; pub struct NetController { - db: PatchDb, + db: TypedPatchDb, pub(super) tor: TorController, pub(super) vhost: VHostController, pub(super) dns: DnsController, @@ -36,7 +35,7 @@ pub struct NetController { impl NetController { #[instrument(skip_all)] pub async fn init( - db: PatchDb, + db: TypedPatchDb, tor_control: SocketAddr, tor_socks: SocketAddr, dns_bind: &[SocketAddr], @@ -394,14 +393,23 @@ impl NetService { pub fn get_ext_port(&self, host_id: HostId, internal_port: u16) -> Result { let host_id_binds = self.binds.get_key_value(&host_id); match host_id_binds { - Some((id, binds)) => { + Some((_, binds)) => { if let Some(ext_port_info) = binds.lan.get(&internal_port) { Ok(ext_port_info.0) } else { - Err(Error::new(eyre!("Internal Port {} not found in NetService binds", internal_port), crate::ErrorKind::NotFound)) + Err(Error::new( + eyre!( + "Internal Port {} not found in NetService binds", + internal_port + ), + crate::ErrorKind::NotFound, + )) } - }, - None => Err(Error::new(eyre!("HostID {} not found in NetService binds", host_id), crate::ErrorKind::NotFound)) + } + None => Err(Error::new( + eyre!("HostID {} not found in NetService binds", host_id), + crate::ErrorKind::NotFound, + )), } } } diff --git a/core/startos/src/net/ssl.rs b/core/startos/src/net/ssl.rs index 245881c55..382006072 100644 --- a/core/startos/src/net/ssl.rs +++ b/core/startos/src/net/ssl.rs @@ -17,7 +17,6 @@ use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509}; use openssl::*; use patch_db::HasModel; use serde::{Deserialize, Serialize}; -use tokio::sync::Mutex; use tracing::instrument; use crate::account::AccountInfo; diff --git a/core/startos/src/net/static_server.rs b/core/startos/src/net/static_server.rs index fa71672b3..85737598b 100644 --- a/core/startos/src/net/static_server.rs +++ b/core/startos/src/net/static_server.rs @@ -24,18 +24,19 @@ use tokio::io::BufReader; use tokio_util::io::ReaderStream; use crate::context::{DiagnosticContext, InstallContext, RpcContext, SetupContext}; -use crate::core::rpc_continuations::RequestGuid; use crate::db::subscribe; use crate::hostname::Hostname; use crate::middleware::auth::{Auth, HasValidSession}; use crate::middleware::cors::Cors; use crate::middleware::db::SyncDb; use crate::middleware::diagnostic::DiagnosticMode; +use crate::rpc_continuations::RequestGuid; use crate::{diagnostic_api, install_api, main_api, setup_api, Error, ErrorKind, ResultExt}; const NOT_FOUND: &[u8] = b"Not Found"; const METHOD_NOT_ALLOWED: &[u8] = b"Method Not Allowed"; const NOT_AUTHORIZED: &[u8] = b"Not Authorized"; +const INTERNAL_SERVER_ERROR: &[u8] = b"Internal Server Error"; #[cfg(all(feature = "daemon", not(feature = "test")))] const EMBEDDED_UIS: Dir<'_> = @@ -112,7 +113,7 @@ pub fn main_ui_server_router(ctx: RpcContext) -> Router { .route("/rpc/*path", { let ctx = ctx.clone(); post( - Server::new(move || ready(Ok(ctx.clone())), main_api()) + Server::new(move || ready(Ok(ctx.clone())), main_api::()) .middleware(Cors::new()) .middleware(Auth::new()) .middleware(SyncDb::new()), @@ -140,7 +141,7 @@ pub fn main_ui_server_router(ctx: RpcContext) -> Router { tracing::debug!("No Guid Path"); bad_request() } - Some(guid) => match ctx.get_ws_continuation_handler(&guid).await { + Some(guid) => match ctx.rpc_continuations.get_ws_handler(&guid).await { Some(cont) => ws.on_upgrade(cont), _ => not_found(), }, @@ -163,7 +164,7 @@ pub fn main_ui_server_router(ctx: RpcContext) -> Router { tracing::debug!("No Guid Path"); bad_request() } - Some(guid) => match ctx.get_rest_continuation_handler(&guid).await { + Some(guid) => match ctx.rpc_continuations.get_rest_handler(&guid).await { None => not_found(), Some(cont) => cont(request).await.unwrap_or_else(server_error), }, @@ -216,7 +217,7 @@ async fn if_authorized< ) -> Result { if let Err(e) = HasValidSession::from_header(parts.headers.get(http::header::COOKIE), ctx).await { - un_authorized(e, parts.uri.path()) + Ok(unauthorized(e, parts.uri.path())) } else { f().await } @@ -305,17 +306,17 @@ async fn main_start_os_ui(req: Request, ctx: RpcContext) -> Result Result { +pub fn unauthorized(err: Error, path: &str) -> Response { tracing::warn!("unauthorized for {} @{:?}", err, path); tracing::debug!("{:?}", err); - Ok(Response::builder() + Response::builder() .status(StatusCode::UNAUTHORIZED) .body(NOT_AUTHORIZED.into()) - .unwrap()) + .unwrap() } /// HTTP status code 404 -fn not_found() -> Response { +pub fn not_found() -> Response { Response::builder() .status(StatusCode::NOT_FOUND) .body(NOT_FOUND.into()) @@ -323,21 +324,23 @@ fn not_found() -> Response { } /// HTTP status code 405 -fn method_not_allowed() -> Response { +pub fn method_not_allowed() -> Response { Response::builder() .status(StatusCode::METHOD_NOT_ALLOWED) .body(METHOD_NOT_ALLOWED.into()) .unwrap() } -fn server_error(err: Error) -> Response { +pub fn server_error(err: Error) -> Response { + tracing::error!("internal server error: {}", err); + tracing::debug!("{:?}", err); Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(err.to_string().into()) + .body(INTERNAL_SERVER_ERROR.into()) .unwrap() } -fn bad_request() -> Response { +pub fn bad_request() -> Response { Response::builder() .status(StatusCode::BAD_REQUEST) .body(Body::empty()) diff --git a/core/startos/src/net/tor.rs b/core/startos/src/net/tor.rs index dfca0fc0b..31eae5fab 100644 --- a/core/startos/src/net/tor.rs +++ b/core/startos/src/net/tor.rs @@ -12,8 +12,7 @@ use helpers::NonDetachingJoinHandle; use itertools::Itertools; use lazy_static::lazy_static; use regex::Regex; -use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{command, from_fn_async, AnyContext, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::net::TcpStream; use tokio::process::Command; @@ -25,10 +24,7 @@ use tracing::instrument; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; -use crate::logs::{ - cli_logs_generic_follow, cli_logs_generic_nofollow, fetch_logs, follow_logs, journalctl, - LogFollowResponse, LogResponse, LogSource, -}; +use crate::logs::{journalctl, LogSource, LogsParams}; use crate::prelude::*; use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; use crate::util::Invoke; @@ -86,23 +82,27 @@ lazy_static! { static ref PROGRESS_REGEX: Regex = Regex::new("PROGRESS=([0-9]+)").unwrap(); } -pub fn tor() -> ParentHandler { +pub fn tor() -> ParentHandler { ParentHandler::new() .subcommand( "list-services", from_fn_async(list_services) .with_display_serializable() - .with_custom_display_fn::(|handle, result| { + .with_custom_display_fn(|handle, result| { Ok(display_services(handle.params, result)) }) - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand("logs", logs()) + .subcommand( + "logs", + from_fn_async(crate::logs::cli_logs::).no_display(), + ) .subcommand( "reset", from_fn_async(reset) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } #[derive(Deserialize, Serialize, Parser, TS)] @@ -143,89 +143,10 @@ pub async fn list_services(ctx: RpcContext, _: Empty) -> Result, - #[arg(short = 'c', long = "cursor")] - cursor: Option, - #[arg(short = 'B', long = "before")] - #[serde(default)] - before: bool, - #[arg(short = 'f', long = "follow")] - #[serde(default)] - follow: bool, -} - -pub fn logs() -> ParentHandler { - ParentHandler::new() - .root_handler( - from_fn_async(cli_logs) - .no_display() - .with_inherited(|params, _| params), - ) - .root_handler( - from_fn_async(logs_nofollow) - .with_inherited(|params, _| params) - .no_cli(), - ) - .subcommand( - "follow", - from_fn_async(logs_follow) - .with_inherited(|params, _| params) - .no_cli(), - ) -} -pub async fn cli_logs( - ctx: CliContext, - _: Empty, - LogsParams { - limit, - cursor, - before, - follow, - }: LogsParams, -) -> Result<(), RpcError> { - if follow { - if cursor.is_some() { - return Err(RpcError::from(Error::new( - eyre!("The argument '--cursor ' cannot be used with '--follow'"), - crate::ErrorKind::InvalidRequest, - ))); - } - if before { - return Err(RpcError::from(Error::new( - eyre!("The argument '--before' cannot be used with '--follow'"), - crate::ErrorKind::InvalidRequest, - ))); - } - cli_logs_generic_follow(ctx, "net.tor.logs.follow", None, limit).await - } else { - cli_logs_generic_nofollow(ctx, "net.tor.logs", None, limit, cursor, before).await - } -} -pub async fn logs_nofollow( - _: AnyContext, - _: Empty, - LogsParams { - limit, - cursor, - before, - .. - }: LogsParams, -) -> Result { - fetch_logs(LogSource::Unit(SYSTEMD_UNIT), limit, cursor, before).await -} - -pub async fn logs_follow( - ctx: RpcContext, - _: Empty, - LogsParams { limit, .. }: LogsParams, -) -> Result { - follow_logs(ctx, LogSource::Unit(SYSTEMD_UNIT), limit).await +pub fn logs() -> ParentHandler { + crate::logs::logs::(|_: &RpcContext, _| async { + Ok(LogSource::Unit(SYSTEMD_UNIT)) + }) } fn event_handler(_event: AsyncEvent<'static>) -> BoxFuture<'static, Result<(), ConnError>> { diff --git a/core/startos/src/net/vhost.rs b/core/startos/src/net/vhost.rs index 47cf30ad5..8dc1e6da3 100644 --- a/core/startos/src/net/vhost.rs +++ b/core/startos/src/net/vhost.rs @@ -19,6 +19,7 @@ use tokio_rustls::{LazyConfigAcceptor, TlsConnector}; use tracing::instrument; use ts_rs::TS; +use crate::db::model::Database; use crate::prelude::*; use crate::util::io::{BackTrackingReader, TimeoutStream}; use crate::util::serde::MaybeUtf8String; @@ -26,11 +27,11 @@ use crate::util::serde::MaybeUtf8String; // not allowed: <=1024, >=32768, 5355, 5432, 9050, 6010, 9051, 5353 pub struct VHostController { - db: PatchDb, + db: TypedPatchDb, servers: Mutex>, } impl VHostController { - pub fn new(db: PatchDb) -> Self { + pub fn new(db: TypedPatchDb) -> Self { Self { db, servers: Mutex::new(BTreeMap::new()), @@ -100,7 +101,7 @@ struct VHostServer { } impl VHostServer { #[instrument(skip_all)] - async fn new(port: u16, db: PatchDb) -> Result { + async fn new(port: u16, db: TypedPatchDb) -> Result { // check if port allowed let listener = TcpListener::bind(SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), port)) .await diff --git a/core/startos/src/net/wifi.rs b/core/startos/src/net/wifi.rs index 93f610a42..298fad71f 100644 --- a/core/startos/src/net/wifi.rs +++ b/core/startos/src/net/wifi.rs @@ -8,7 +8,7 @@ use clap::Parser; use isocountry::CountryCode; use lazy_static::lazy_static; use regex::Regex; -use rpc_toolkit::{command, from_fn_async, AnyContext, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::process::Command; use tokio::sync::RwLock; @@ -17,6 +17,7 @@ use ts_rs::TS; use crate::context::{CliContext, RpcContext}; use crate::db::model::public::WifiInfo; +use crate::db::model::Database; use crate::net::utils::find_wifi_iface; use crate::prelude::*; use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; @@ -36,57 +37,55 @@ pub fn wifi_manager(ctx: &RpcContext) -> Result<&WifiManager, Error> { } } -pub fn wifi() -> ParentHandler { +pub fn wifi() -> ParentHandler { ParentHandler::new() .subcommand( "add", from_fn_async(add) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "connect", from_fn_async(connect) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "delete", from_fn_async(delete) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "get", from_fn_async(get) .with_display_serializable() - .with_custom_display_fn::(|handle, result| { + .with_custom_display_fn(|handle, result| { Ok(display_wifi_info(handle.params, result)) }) - .with_remote_cli::(), + .with_call_remote::(), ) - .subcommand("country", country()) - .subcommand("available", available()) + .subcommand("country", country::()) + .subcommand("available", available::()) } -pub fn available() -> ParentHandler { +pub fn available() -> ParentHandler { ParentHandler::new().subcommand( "get", from_fn_async(get_available) .with_display_serializable() - .with_custom_display_fn::(|handle, result| { - Ok(display_wifi_list(handle.params, result)) - }) - .with_remote_cli::(), + .with_custom_display_fn(|handle, result| Ok(display_wifi_list(handle.params, result))) + .with_call_remote::(), ) } -pub fn country() -> ParentHandler { +pub fn country() -> ParentHandler { ParentHandler::new().subcommand( "set", from_fn_async(set_country) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } @@ -113,7 +112,7 @@ pub async fn add(ctx: RpcContext, AddParams { ssid, password }: AddParams) -> Re )); } async fn add_procedure( - db: PatchDb, + db: TypedPatchDb, wifi_manager: WifiManager, ssid: &Ssid, password: &Psk, @@ -170,7 +169,7 @@ pub async fn connect(ctx: RpcContext, SsidParams { ssid }: SsidParams) -> Result )); } async fn connect_procedure( - db: PatchDb, + db: TypedPatchDb, wifi_manager: WifiManager, ssid: &Ssid, ) -> Result<(), Error> { @@ -718,7 +717,7 @@ impl WpaCli { Ok(()) } - pub async fn save_config(&mut self, db: PatchDb) -> Result<(), Error> { + pub async fn save_config(&mut self, db: TypedPatchDb) -> Result<(), Error> { let new_country = self.get_country_low().await?; db.mutate(|d| { d.as_public_mut() @@ -758,7 +757,11 @@ impl WpaCli { .collect()) } #[instrument(skip_all)] - pub async fn select_network(&mut self, db: PatchDb, ssid: &Ssid) -> Result { + pub async fn select_network( + &mut self, + db: TypedPatchDb, + ssid: &Ssid, + ) -> Result { let m_id = self.check_active_network(ssid).await?; match m_id { None => Err(Error::new( @@ -810,7 +813,11 @@ impl WpaCli { } } #[instrument(skip_all)] - pub async fn remove_network(&mut self, db: PatchDb, ssid: &Ssid) -> Result { + pub async fn remove_network( + &mut self, + db: TypedPatchDb, + ssid: &Ssid, + ) -> Result { let found_networks = self.find_networks(ssid).await?; if found_networks.is_empty() { return Ok(true); @@ -824,7 +831,7 @@ impl WpaCli { #[instrument(skip_all)] pub async fn set_add_network( &mut self, - db: PatchDb, + db: TypedPatchDb, ssid: &Ssid, psk: &Psk, ) -> Result<(), Error> { @@ -833,7 +840,12 @@ impl WpaCli { Ok(()) } #[instrument(skip_all)] - pub async fn add_network(&mut self, db: PatchDb, ssid: &Ssid, psk: &Psk) -> Result<(), Error> { + pub async fn add_network( + &mut self, + db: TypedPatchDb, + ssid: &Ssid, + psk: &Psk, + ) -> Result<(), Error> { self.add_network_low(ssid, psk).await?; self.save_config(db).await?; Ok(()) diff --git a/core/startos/src/notifications.rs b/core/startos/src/notifications.rs index 8a5732462..fde3d5e80 100644 --- a/core/startos/src/notifications.rs +++ b/core/startos/src/notifications.rs @@ -8,7 +8,7 @@ use clap::Parser; use color_eyre::eyre::eyre; use imbl_value::InternedString; use models::PackageId; -use rpc_toolkit::{command, from_fn_async, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; @@ -21,31 +21,31 @@ use crate::util::clap::FromStrParser; use crate::util::serde::HandlerExtSerde; // #[command(subcommands(list, delete, delete_before, create))] -pub fn notification() -> ParentHandler { +pub fn notification() -> ParentHandler { ParentHandler::new() .subcommand( "list", from_fn_async(list) .with_display_serializable() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "delete", from_fn_async(delete) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "delete-before", from_fn_async(delete_before) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "create", from_fn_async(create) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } diff --git a/core/startos/src/os_install/mod.rs b/core/startos/src/os_install/mod.rs index 4bbbe70a6..c3931b236 100644 --- a/core/startos/src/os_install/mod.rs +++ b/core/startos/src/os_install/mod.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use clap::Parser; use color_eyre::eyre::eyre; use models::Error; -use rpc_toolkit::{command, from_fn_async, AnyContext, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::process::Command; use ts_rs::TS; @@ -13,11 +13,15 @@ use crate::context::{CliContext, InstallContext}; use crate::disk::mount::filesystem::bind::Bind; use crate::disk::mount::filesystem::block_dev::BlockDev; use crate::disk::mount::filesystem::efivarfs::EfiVarFs; +use crate::disk::mount::filesystem::overlayfs::OverlayFs; use crate::disk::mount::filesystem::{MountType, ReadWrite}; use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard}; use crate::disk::util::{DiskInfo, PartitionTable}; use crate::disk::OsPartitionInfo; use crate::net::utils::find_eth_iface; +use crate::prelude::*; +use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; +use crate::util::io::TmpDir; use crate::util::serde::IoFormat; use crate::util::Invoke; use crate::ARCH; @@ -25,33 +29,33 @@ use crate::ARCH; mod gpt; mod mbr; -pub fn install() -> ParentHandler { +pub fn install() -> ParentHandler { ParentHandler::new() - .subcommand("disk", disk()) + .subcommand("disk", disk::()) .subcommand( "execute", - from_fn_async(execute) + from_fn_async(execute::) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "reboot", from_fn_async(reboot) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } -pub fn disk() -> ParentHandler { +pub fn disk() -> ParentHandler { ParentHandler::new().subcommand( "list", from_fn_async(list) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) } -pub async fn list() -> Result, Error> { +pub async fn list(_: InstallContext) -> Result, Error> { let skip = match async { Ok::<_, Error>( Path::new( @@ -122,8 +126,8 @@ pub struct ExecuteParams { overwrite: bool, } -pub async fn execute( - _: AnyContext, +pub async fn execute( + _: C, ExecuteParams { logicalname, mut overwrite, @@ -215,44 +219,62 @@ pub async fn execute( .arg("rootfs") .invoke(crate::ErrorKind::DiskManagement) .await?; - let rootfs = TmpMountGuard::mount(&BlockDev::new(&part_info.root), ReadWrite).await?; + + let config_path = rootfs.path().join("config"); + if tokio::fs::metadata("/tmp/config.bak").await.is_ok() { + if tokio::fs::metadata(&config_path).await.is_ok() { + tokio::fs::remove_dir_all(&config_path).await?; + } Command::new("cp") .arg("-r") .arg("/tmp/config.bak") - .arg(rootfs.path().join("config")) + .arg(&config_path) .invoke(crate::ErrorKind::Filesystem) .await?; } else { - tokio::fs::create_dir(rootfs.path().join("config")).await?; + tokio::fs::create_dir_all(&config_path).await?; } - tokio::fs::create_dir(rootfs.path().join("next")).await?; - let current = rootfs.path().join("current"); - tokio::fs::create_dir(¤t).await?; - tokio::fs::create_dir(current.join("boot")).await?; - let boot = MountGuard::mount( + let images_path = rootfs.path().join("images"); + tokio::fs::create_dir_all(&images_path).await?; + let image_path = images_path + .join(hex::encode( + &MultiCursorFile::from( + tokio::fs::File::open("/run/live/medium/live/filesystem.squashfs").await?, + ) + .blake3_mmap() + .await? + .as_bytes()[..16], + )) + .with_extension("rootfs"); + tokio::fs::copy("/run/live/medium/live/filesystem.squashfs", &image_path).await?; + // TODO: check hash of fs + let unsquash_target = TmpDir::new().await?; + let bootfs = MountGuard::mount( &BlockDev::new(&part_info.boot), - current.join("boot"), + unsquash_target.join("boot"), ReadWrite, ) .await?; - - let efi = if let Some(efi) = &part_info.efi { - Some(MountGuard::mount(&BlockDev::new(efi), current.join("boot/efi"), ReadWrite).await?) - } else { - None - }; - Command::new("unsquashfs") .arg("-n") .arg("-f") .arg("-d") - .arg(¤t) + .arg(&*unsquash_target) .arg("/run/live/medium/live/filesystem.squashfs") + .arg("boot") .invoke(crate::ErrorKind::Filesystem) .await?; + bootfs.unmount(true).await?; + unsquash_target.delete().await?; + Command::new("ln") + .arg("-rsf") + .arg(&image_path) + .arg(config_path.join("current.rootfs")) + .invoke(ErrorKind::DiskManagement) + .await?; tokio::fs::write( rootfs.path().join("config/config.yaml"), @@ -264,8 +286,55 @@ pub async fn execute( ) .await?; + let lower = TmpMountGuard::mount(&BlockDev::new(&image_path), MountType::ReadOnly).await?; + let work = config_path.join("work"); + let upper = config_path.join("overlay"); + let overlay = + TmpMountGuard::mount(&OverlayFs::new(&lower.path(), &upper, &work), ReadWrite).await?; + + let boot = MountGuard::mount( + &BlockDev::new(&part_info.boot), + overlay.path().join("boot"), + ReadWrite, + ) + .await?; + let efi = if let Some(efi) = &part_info.efi { + Some( + MountGuard::mount( + &BlockDev::new(efi), + overlay.path().join("boot/efi"), + ReadWrite, + ) + .await?, + ) + } else { + None + }; + let start_os_fs = MountGuard::mount( + &Bind::new(rootfs.path()), + overlay.path().join("media/startos/root"), + MountType::ReadOnly, + ) + .await?; + let dev = MountGuard::mount(&Bind::new("/dev"), overlay.path().join("dev"), ReadWrite).await?; + let proc = + MountGuard::mount(&Bind::new("/proc"), overlay.path().join("proc"), ReadWrite).await?; + let sys = MountGuard::mount(&Bind::new("/sys"), overlay.path().join("sys"), ReadWrite).await?; + let efivarfs = if tokio::fs::metadata("/sys/firmware/efi").await.is_ok() { + Some( + MountGuard::mount( + &EfiVarFs, + overlay.path().join("sys/firmware/efi/efivars"), + ReadWrite, + ) + .await?, + ) + } else { + None + }; + tokio::fs::write( - current.join("etc/fstab"), + overlay.path().join("etc/fstab"), format!( include_str!("fstab.template"), boot = part_info.boot.display(), @@ -280,42 +349,20 @@ pub async fn execute( .await?; Command::new("chroot") - .arg(¤t) + .arg(overlay.path()) .arg("systemd-machine-id-setup") .invoke(crate::ErrorKind::Systemd) .await?; Command::new("chroot") - .arg(¤t) + .arg(overlay.path()) .arg("ssh-keygen") .arg("-A") .invoke(crate::ErrorKind::OpenSsh) .await?; - let start_os_fs = MountGuard::mount( - &Bind::new(rootfs.path()), - current.join("media/embassy/embassyfs"), - MountType::ReadOnly, - ) - .await?; - let dev = MountGuard::mount(&Bind::new("/dev"), current.join("dev"), ReadWrite).await?; - let proc = MountGuard::mount(&Bind::new("/proc"), current.join("proc"), ReadWrite).await?; - let sys = MountGuard::mount(&Bind::new("/sys"), current.join("sys"), ReadWrite).await?; - let efivarfs = if tokio::fs::metadata("/sys/firmware/efi").await.is_ok() { - Some( - MountGuard::mount( - &EfiVarFs, - current.join("sys/firmware/efi/efivars"), - ReadWrite, - ) - .await?, - ) - } else { - None - }; - let mut install = Command::new("chroot"); - install.arg(¤t).arg("grub-install"); + install.arg(overlay.path()).arg("grub-install"); if tokio::fs::metadata("/sys/firmware/efi").await.is_err() { install.arg("--target=i386-pc"); } else { @@ -331,7 +378,7 @@ pub async fn execute( .await?; Command::new("chroot") - .arg(¤t) + .arg(overlay.path()) .arg("update-grub2") .invoke(crate::ErrorKind::Grub) .await?; @@ -346,7 +393,13 @@ pub async fn execute( efi.unmount(false).await?; } boot.unmount(false).await?; + + overlay.unmount().await?; + tokio::fs::remove_dir_all(&work).await?; + lower.unmount().await?; + rootfs.unmount().await?; + Ok(()) } diff --git a/core/startos/src/progress.rs b/core/startos/src/progress.rs index eec637575..9a22405ca 100644 --- a/core/startos/src/progress.rs +++ b/core/startos/src/progress.rs @@ -11,7 +11,7 @@ use tokio::io::{AsyncSeek, AsyncWrite}; use tokio::sync::{mpsc, watch}; use ts_rs::TS; -use crate::db::model::DatabaseModel; +use crate::db::model::{Database, DatabaseModel}; use crate::prelude::*; lazy_static::lazy_static! { @@ -23,6 +23,7 @@ lazy_static::lazy_static! { #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, TS)] #[serde(untagged)] pub enum Progress { + NotStarted(()), Complete(bool), Progress { #[ts(type = "number")] @@ -33,10 +34,13 @@ pub enum Progress { } impl Progress { pub fn new() -> Self { - Progress::Complete(false) + Progress::NotStarted(()) } pub fn update_bar(self, bar: &ProgressBar) { match self { + Self::NotStarted(()) => { + bar.set_style(SPINNER.clone()); + } Self::Complete(false) => { bar.set_style(SPINNER.clone()); bar.tick(); @@ -60,9 +64,15 @@ impl Progress { } } } + pub fn start(&mut self) { + *self = match *self { + Self::NotStarted(()) => Self::Complete(false), + a => a, + }; + } pub fn set_done(&mut self, done: u64) { *self = match *self { - Self::Complete(false) => Self::Progress { done, total: None }, + Self::Complete(false) | Self::NotStarted(()) => Self::Progress { done, total: None }, Self::Progress { mut done, total } => { if let Some(total) = total { if done > total { @@ -76,7 +86,7 @@ impl Progress { } pub fn set_total(&mut self, total: u64) { *self = match *self { - Self::Complete(false) => Self::Progress { + Self::Complete(false) | Self::NotStarted(()) => Self::Progress { done: 0, total: Some(total), }, @@ -104,12 +114,15 @@ impl Progress { pub fn complete(&mut self) { *self = Self::Complete(true); } + pub fn is_complete(&self) -> bool { + matches!(self, Self::Complete(true)) + } } impl std::ops::Add for Progress { type Output = Self; fn add(self, rhs: u64) -> Self::Output { match self { - Self::Complete(false) => Self::Progress { + Self::Complete(false) | Self::NotStarted(()) => Self::Progress { done: rhs, total: None, }, @@ -218,7 +231,7 @@ impl FullProgressTracker { } pub fn sync_to_db( mut self, - db: PatchDb, + db: TypedPatchDb, deref: DerefFn, min_interval: Option, ) -> impl Future> + 'static @@ -308,6 +321,9 @@ impl PhaseProgressTrackerHandle { } } } + pub fn start(&mut self) { + self.progress.send_modify(|p| p.start()); + } pub fn set_done(&mut self, done: u64) { self.progress.send_modify(|p| p.set_done(done)); self.update_overall(); @@ -324,6 +340,12 @@ impl PhaseProgressTrackerHandle { self.progress.send_modify(|p| p.complete()); self.update_overall(); } + pub fn writer(self, writer: W) -> ProgressTrackerWriter { + ProgressTrackerWriter { + writer, + progress: self, + } + } } impl std::ops::AddAssign for PhaseProgressTrackerHandle { fn add_assign(&mut self, rhs: u64) { diff --git a/core/startos/src/properties.rs b/core/startos/src/properties.rs index f8753ab10..e24b14965 100644 --- a/core/startos/src/properties.rs +++ b/core/startos/src/properties.rs @@ -1,7 +1,6 @@ use clap::Parser; use imbl_value::{json, Value}; use models::PackageId; -use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use crate::context::RpcContext; diff --git a/core/startos/src/registry/admin.rs b/core/startos/src/registry/admin.rs index d416ebdbd..b09fa05aa 100644 --- a/core/startos/src/registry/admin.rs +++ b/core/startos/src/registry/admin.rs @@ -1,234 +1,255 @@ +use std::collections::BTreeMap; use std::path::PathBuf; -use std::time::Duration; use clap::Parser; -use color_eyre::eyre::eyre; -use console::style; -use futures::StreamExt; -use indicatif::{ProgressBar, ProgressStyle}; -use reqwest::{header, Body, Client, Url}; -use rpc_toolkit::command; +use itertools::Itertools; +use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use ts_rs::TS; use crate::context::CliContext; -use crate::s9pk::S9pk; -use crate::{Error, ErrorKind}; +use crate::prelude::*; +use crate::registry::context::RegistryContext; +use crate::registry::signer::{ContactInfo, SignerInfo, SignerKey}; +use crate::registry::RegistryDatabase; +use crate::rpc_continuations::RequestGuid; +use crate::util::serde::{display_serializable, HandlerExtSerde, Pem, WithIoFormat}; -async fn registry_user_pass(location: &str) -> Result<(Url, String, String), Error> { - let mut url = Url::parse(location)?; - let user = url.username().to_string(); - let pass = url.password().map(str::to_string); - if user.is_empty() || url.path() != "/" { - return Err(Error::new( - eyre!("{location:?} is not like \"https://user@registry.example.com/\""), - ErrorKind::ParseUrl, - )); +pub fn admin_api() -> ParentHandler { + ParentHandler::new() + .subcommand("signer", signers_api::()) + .subcommand("add", from_fn_async(add_admin).no_cli()) + .subcommand("add", from_fn_async(cli_add_admin).no_display()) + .subcommand( + "list", + from_fn_async(list_admins) + .with_display_serializable() + .with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result))) + .with_call_remote::(), + ) +} + +fn signers_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "list", + from_fn_async(list_signers) + .with_metadata("admin", Value::Bool(true)) + .with_display_serializable() + .with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result))) + .with_call_remote::(), + ) + .subcommand( + "add", + from_fn_async(add_signer) + .with_metadata("admin", Value::Bool(true)) + .no_cli(), + ) + .subcommand("add", from_fn_async(cli_add_signer).no_display()) +} + +impl Model> { + pub fn get_signer(&self, key: &SignerKey) -> Result { + self.as_entries()? + .into_iter() + .map(|(guid, s)| Ok::<_, Error>((guid, s.as_keys().de()?))) + .filter_ok(|(_, s)| s.contains(key)) + .next() + .transpose()? + .map(|(a, _)| a) + .ok_or_else(|| Error::new(eyre!("unknown signer"), ErrorKind::Authorization)) } - let _ = url.set_username(""); - let _ = url.set_password(None); - let pass = match pass { - Some(p) => p, - None => { - let pass_prompt = format!("{} Password for {user}: ", style("?").yellow()); - tokio::task::spawn_blocking(move || rpassword::prompt_password(pass_prompt)) - .await - .unwrap()? + pub fn get_signer_info(&self, key: &SignerKey) -> Result<(RequestGuid, SignerInfo), Error> { + self.as_entries()? + .into_iter() + .map(|(guid, s)| Ok::<_, Error>((guid, s.de()?))) + .filter_ok(|(_, s)| s.keys.contains(key)) + .next() + .transpose()? + .ok_or_else(|| Error::new(eyre!("unknown signer"), ErrorKind::Authorization)) + } + + pub fn add_signer(&mut self, signer: &SignerInfo) -> Result<(), Error> { + if let Some((guid, s)) = self + .as_entries()? + .into_iter() + .map(|(guid, s)| Ok::<_, Error>((guid, s.de()?))) + .filter_ok(|(_, s)| !s.keys.is_disjoint(&signer.keys)) + .next() + .transpose()? + { + return Err(Error::new( + eyre!( + "A signer {} ({}) already exists with a matching key", + guid, + s.name + ), + ErrorKind::InvalidRequest, + )); } - }; - Ok((url, user.to_string(), pass.to_string())) -} - -#[derive(serde::Serialize, Debug)] -struct Package { - id: String, - version: String, - arches: Option>, -} - -async fn do_index( - httpc: &Client, - mut url: Url, - user: &str, - pass: &str, - pkg: &Package, -) -> Result<(), Error> { - url.set_path("/admin/v0/index"); - let req = httpc - .post(url) - .header(header::ACCEPT, "text/plain") - .basic_auth(user, Some(pass)) - .json(pkg) - .build()?; - let res = httpc.execute(req).await?; - if !res.status().is_success() { - let info = res.text().await?; - return Err(Error::new(eyre!("{}", info), ErrorKind::Registry)); + self.insert(&RequestGuid::new(), signer) } - Ok(()) } -async fn do_upload( - httpc: &Client, - mut url: Url, - user: &str, - pass: &str, - pkg_id: &str, - body: Body, -) -> Result<(), Error> { - url.set_path("/admin/v0/upload"); - let req = httpc - .post(url) - .header(header::ACCEPT, "text/plain") - .query(&["id", pkg_id]) - .basic_auth(user, Some(pass)) - .body(body) - .build()?; - let res = httpc.execute(req).await?; - if !res.status().is_success() { - let info = res.text().await?; - return Err(Error::new(eyre!("{}", info), ErrorKind::Registry)); +pub async fn list_signers( + ctx: RegistryContext, +) -> Result, Error> { + ctx.db.peek().await.into_index().into_signers().de() +} + +pub fn display_signers(params: WithIoFormat, signers: BTreeMap) { + use prettytable::*; + + if let Some(format) = params.format { + return display_serializable(format, signers); } - Ok(()) + + let mut table = Table::new(); + table.add_row(row![bc => + "ID", + "NAME", + "CONTACT", + "KEYS", + ]); + for (id, info) in signers { + table.add_row(row![ + id.as_ref(), + &info.name, + &info.contact.into_iter().join("\n"), + &info.keys.into_iter().join("\n"), + ]); + } + table.print_tty(false).unwrap(); } -#[derive(Deserialize, Serialize, Parser)] -#[serde(rename_all = "camelCase")] +pub async fn add_signer(ctx: RegistryContext, signer: SignerInfo) -> Result<(), Error> { + ctx.db + .mutate(|db| db.as_index_mut().as_signers_mut().add_signer(&signer)) + .await +} + +#[derive(Debug, Deserialize, Serialize, Parser)] #[command(rename_all = "kebab-case")] -pub struct PublishParams { - location: String, - path: PathBuf, - #[arg(name = "no-verify", long = "no-verify")] - no_verify: bool, - #[arg(name = "no-upload", long = "no-upload")] - no_upload: bool, - #[arg(name = "no-index", long = "no-index")] - no_index: bool, +#[serde(rename_all = "camelCase")] +pub struct CliAddSignerParams { + #[arg(long = "name", short = 'n')] + pub name: String, + #[arg(long = "contact", short = 'c')] + pub contact: Vec, + #[arg(long = "ed25519-key")] + pub ed25519_keys: Vec>, + pub database: Option, } -pub async fn publish( - _: CliContext, - PublishParams { - location, - no_index, - no_upload, - no_verify, - path, - }: PublishParams, +pub async fn cli_add_signer( + HandlerArgs { + context: ctx, + parent_method, + method, + params: + CliAddSignerParams { + name, + contact, + ed25519_keys, + database, + }, + .. + }: HandlerArgs, ) -> Result<(), Error> { - // Prepare for progress bars. - let bytes_bar_style = - ProgressStyle::with_template("{percent}% {wide_bar} [{bytes}/{total_bytes}] [{eta}]") - .unwrap(); - let plain_line_style = - ProgressStyle::with_template("{prefix:.bold.dim} {wide_msg}...").unwrap(); - let spinner_line_style = - ProgressStyle::with_template("{prefix:.bold.dim} {spinner} {wide_msg}...").unwrap(); - - // Read the file to get manifest information and check validity.. - // Open file right away so it can not change out from under us. - let file = tokio::fs::File::open(&path).await?; - - let manifest = if no_verify { - let pb = ProgressBar::new(1) - .with_style(spinner_line_style.clone()) - .with_prefix("[1/3]") - .with_message("Querying s9pk"); - pb.enable_steady_tick(Duration::from_millis(200)); - let s9pk = S9pk::open(&path, None, false).await?; - let m = s9pk.as_manifest().clone(); - pb.set_style(plain_line_style.clone()); - pb.abandon(); - m - } else { - let pb = ProgressBar::new(1) - .with_style(spinner_line_style.clone()) - .with_prefix("[1/3]") - .with_message("Verifying s9pk"); - pb.enable_steady_tick(Duration::from_millis(200)); - let s9pk = S9pk::open(&path, None, false).await?; - // s9pk.validate().await?; - todo!(); - let m = s9pk.as_manifest().clone(); - pb.set_style(plain_line_style.clone()); - pb.abandon(); - m + let signer = SignerInfo { + name, + contact, + keys: ed25519_keys.into_iter().map(SignerKey::Ed25519).collect(), }; - let pkg = Package { - id: manifest.id.to_string(), - version: manifest.version.to_string(), - arches: manifest.hardware_requirements.arch.clone(), - }; - println!("{} id = {}", style(">").green(), pkg.id); - println!("{} version = {}", style(">").green(), pkg.version); - if let Some(arches) = &pkg.arches { - println!("{} arches = {:?}", style(">").green(), arches); + if let Some(database) = database { + TypedPatchDb::::load(PatchDb::open(database).await?) + .await? + .mutate(|db| db.as_index_mut().as_signers_mut().add_signer(&signer)) + .await?; } else { - println!( - "{} No architecture listed in hardware_requirements", - style(">").red() - ); - } - - // Process the url and get the user's password. - let (registry, user, pass) = registry_user_pass(&location).await?; - - // Now prepare a stream of the file which will show a progress bar as it is consumed. - let file_size = file.metadata().await?.len(); - let file_stream = tokio_util::io::ReaderStream::new(file); - ProgressBar::new(0) - .with_style(plain_line_style.clone()) - .with_prefix("[2/3]") - .with_message("Uploading s9pk") - .abandon(); - let pb = ProgressBar::new(file_size).with_style(bytes_bar_style.clone()); - let stream_pb = pb.clone(); - let file_stream = file_stream.inspect(move |bytes| { - if let Ok(bytes) = bytes { - stream_pb.inc(bytes.len() as u64); - } - }); - - let httpc = Client::builder().build().unwrap(); - // And upload! - if no_upload { - println!("{} Skipping upload", style(">").yellow()); - } else { - do_upload( - &httpc, - registry.clone(), - &user, - &pass, - &pkg.id, - Body::wrap_stream(file_stream), + ctx.call_remote::( + &parent_method.into_iter().chain(method).join("."), + to_value(&signer)?, ) .await?; } - pb.finish_and_clear(); + Ok(()) +} - // Also index, so it will show up in the registry. - let pb = ProgressBar::new(0) - .with_style(spinner_line_style.clone()) - .with_prefix("[3/3]") - .with_message("Indexing registry"); - pb.enable_steady_tick(Duration::from_millis(200)); - if no_index { - println!("{} Skipping index", style(">").yellow()); +#[derive(Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct AddAdminParams { + #[ts(type = "string")] + pub signer: RequestGuid, +} + +pub async fn add_admin( + ctx: RegistryContext, + AddAdminParams { signer }: AddAdminParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + ensure_code!( + db.as_index().as_signers().contains_key(&signer)?, + ErrorKind::InvalidRequest, + "unknown signer {signer}" + ); + db.as_admins_mut().mutate(|a| Ok(a.insert(signer)))?; + Ok(()) + }) + .await +} + +#[derive(Debug, Deserialize, Serialize, Parser)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +pub struct CliAddAdminParams { + pub signer: RequestGuid, + pub database: Option, +} + +pub async fn cli_add_admin( + HandlerArgs { + context: ctx, + parent_method, + method, + params: CliAddAdminParams { signer, database }, + .. + }: HandlerArgs, +) -> Result<(), Error> { + if let Some(database) = database { + TypedPatchDb::::load(PatchDb::open(database).await?) + .await? + .mutate(|db| { + ensure_code!( + db.as_index().as_signers().contains_key(&signer)?, + ErrorKind::InvalidRequest, + "unknown signer {signer}" + ); + db.as_admins_mut().mutate(|a| Ok(a.insert(signer)))?; + Ok(()) + }) + .await?; } else { - do_index(&httpc, registry.clone(), &user, &pass, &pkg).await?; - } - pb.set_style(plain_line_style.clone()); - pb.abandon(); - - // All done - if !no_index { - println!( - "{} Package {} is now published to {}", - style(">").green(), - pkg.id, - registry - ); + ctx.call_remote::( + &parent_method.into_iter().chain(method).join("."), + to_value(&AddAdminParams { signer })?, + ) + .await?; } Ok(()) } + +pub async fn list_admins(ctx: RegistryContext) -> Result, Error> { + let db = ctx.db.peek().await; + let admins = db.as_admins().de()?; + Ok(db + .into_index() + .into_signers() + .de()? + .into_iter() + .filter(|(id, _)| admins.contains(id)) + .collect()) +} diff --git a/core/startos/src/registry/asset.rs b/core/startos/src/registry/asset.rs new file mode 100644 index 000000000..c9acced45 --- /dev/null +++ b/core/startos/src/registry/asset.rs @@ -0,0 +1,36 @@ +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use tokio::io::AsyncWrite; +use ts_rs::TS; +use url::Url; + +use crate::prelude::*; +use crate::registry::signer::{AcceptSigners, FileValidator, SignatureInfo}; + +#[derive(Debug, Deserialize, Serialize, HasModel, TS)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +#[ts(export)] +pub struct RegistryAsset { + #[ts(type = "string")] + pub url: Url, + pub signature_info: SignatureInfo, +} +impl AsRef for RegistryAsset { + fn as_ref(&self) -> &RegistryAsset { + self + } +} +impl RegistryAsset { + pub fn validate(&self, accept: AcceptSigners) -> Result { + self.signature_info.validate(accept) + } + pub async fn download( + &self, + client: Client, + dst: &mut (impl AsyncWrite + Unpin + Send + ?Sized), + validator: &FileValidator, + ) -> Result<(), Error> { + validator.download(self.url.clone(), client, dst).await + } +} diff --git a/core/startos/src/registry/auth.rs b/core/startos/src/registry/auth.rs new file mode 100644 index 000000000..187750870 --- /dev/null +++ b/core/startos/src/registry/auth.rs @@ -0,0 +1,214 @@ +use std::collections::BTreeMap; +use std::sync::Arc; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +use axum::body::Body; +use axum::extract::Request; +use axum::response::Response; +use chrono::Utc; +use http_body_util::BodyExt; +use rpc_toolkit::yajrc::RpcError; +use rpc_toolkit::{Middleware, RpcRequest, RpcResponse}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha512}; +use tokio::io::AsyncWriteExt; +use tokio::sync::Mutex; +use ts_rs::TS; + +use crate::prelude::*; +use crate::registry::context::RegistryContext; +use crate::registry::signer::SignerKey; +use crate::util::serde::{Base64, Pem}; + +pub const AUTH_SIG_HEADER: &str = "X-StartOS-Registry-Auth-Sig"; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Metadata { + #[serde(default)] + admin: bool, + #[serde(default)] + get_signer: bool, +} + +#[derive(Clone)] +pub struct Auth { + nonce_cache: Arc>>, // for replay protection + signer: Option>, +} +impl Auth { + pub fn new() -> Self { + Self { + nonce_cache: Arc::new(Mutex::new(BTreeMap::new())), + signer: None, + } + } + async fn handle_nonce(&mut self, nonce: u64) -> Result<(), Error> { + let mut cache = self.nonce_cache.lock().await; + if cache.values().any(|n| *n == nonce) { + return Err(Error::new( + eyre!("replay attack detected"), + ErrorKind::Authorization, + )); + } + while let Some(entry) = cache.first_entry() { + if entry.key().elapsed() > Duration::from_secs(60) { + entry.remove_entry(); + } else { + break; + } + } + Ok(()) + } +} + +#[derive(Serialize, Deserialize, TS)] +pub struct RegistryAdminLogRecord { + pub timestamp: String, + pub name: String, + #[ts(type = "{ id: string | number | null; method: string; params: any }")] + pub request: RpcRequest, + pub key: SignerKey, +} + +#[derive(Serialize, Deserialize)] +pub struct SignatureHeader { + pub timestamp: i64, + pub nonce: u64, + #[serde(flatten)] + pub signer: SignerKey, + pub signature: Base64<[u8; 64]>, +} +impl SignatureHeader { + pub fn sign_ed25519( + key: &ed25519_dalek::SigningKey, + body: &[u8], + context: &str, + ) -> Result { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_secs() as i64) + .unwrap_or_else(|e| e.duration().as_secs() as i64 * -1); + let nonce = rand::random(); + let signer = SignerKey::Ed25519(Pem(key.verifying_key())); + let mut hasher = Sha512::new(); + hasher.update(&i64::to_be_bytes(timestamp)); + hasher.update(&u64::to_be_bytes(nonce)); + hasher.update(body); + let signature = Base64( + key.sign_prehashed(hasher, Some(context.as_bytes()))? + .to_bytes(), + ); + Ok(Self { + timestamp, + nonce, + signer, + signature, + }) + } +} + +impl Middleware for Auth { + type Metadata = Metadata; + async fn process_http_request( + &mut self, + ctx: &RegistryContext, + request: &mut Request, + ) -> Result<(), Response> { + if request.headers().contains_key(AUTH_SIG_HEADER) { + self.signer = Some( + async { + let request = request; + let SignatureHeader { + timestamp, + nonce, + signer, + signature, + } = serde_urlencoded::from_str( + request + .headers() + .get(AUTH_SIG_HEADER) + .or_not_found("missing X-StartOS-Registry-Auth-Sig") + .with_kind(ErrorKind::InvalidRequest)? + .to_str() + .with_kind(ErrorKind::Utf8)?, + ) + .with_kind(ErrorKind::Deserialization)?; + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_secs() as i64) + .unwrap_or_else(|e| e.duration().as_secs() as i64 * -1); + if (now - timestamp).abs() > 30 { + return Err(Error::new( + eyre!("timestamp not within 30s of now"), + ErrorKind::InvalidSignature, + )); + } + self.handle_nonce(nonce).await?; + let body = std::mem::replace(request.body_mut(), Body::empty()) + .collect() + .await + .with_kind(ErrorKind::Network)? + .to_bytes(); + let mut verifier = signer.verifier(); + verifier.update(&i64::to_be_bytes(timestamp)); + verifier.update(&u64::to_be_bytes(nonce)); + verifier.update(&body); + *request.body_mut() = Body::from(body); + + verifier.verify(&*signature, &ctx.hostname)?; + Ok(signer) + } + .await + .map_err(RpcError::from), + ); + } + Ok(()) + } + async fn process_rpc_request( + &mut self, + ctx: &RegistryContext, + metadata: Self::Metadata, + request: &mut RpcRequest, + ) -> Result<(), RpcResponse> { + async move { + let signer = self.signer.take().transpose()?; + if metadata.get_signer { + if let Some(signer) = &signer { + request.params["__auth_signer"] = to_value(signer)?; + } + } + if metadata.admin { + let signer = signer + .ok_or_else(|| Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))?; + let db = ctx.db.peek().await; + let (guid, admin) = db.as_index().as_signers().get_signer_info(&signer)?; + if db.into_admins().de()?.contains(&guid) { + let mut log = tokio::fs::OpenOptions::new() + .create(true) + .append(true) + .open(ctx.datadir.join("admin.log")) + .await?; + log.write_all( + (serde_json::to_string(&RegistryAdminLogRecord { + timestamp: Utc::now().to_rfc3339(), + name: admin.name, + request: request.clone(), + key: signer, + }) + .with_kind(ErrorKind::Serialization)? + + "\n") + .as_bytes(), + ) + .await?; + } else { + return Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization)); + } + } + + Ok(()) + } + .await + .map_err(|e| RpcResponse::from_result(Err(e))) + } +} diff --git a/core/startos/src/registry/context.rs b/core/startos/src/registry/context.rs new file mode 100644 index 000000000..dac833434 --- /dev/null +++ b/core/startos/src/registry/context.rs @@ -0,0 +1,242 @@ +use std::net::{Ipv4Addr, SocketAddr}; +use std::ops::Deref; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use clap::Parser; +use imbl_value::InternedString; +use patch_db::PatchDb; +use rpc_toolkit::yajrc::RpcError; +use rpc_toolkit::{CallRemote, Context, Empty}; +use serde::{Deserialize, Serialize}; +use tokio::sync::broadcast::Sender; +use tracing::instrument; +use url::Url; + +use crate::context::config::{ContextConfig, CONFIG_PATH}; +use crate::context::{CliContext, RpcContext}; +use crate::prelude::*; +use crate::registry::auth::{SignatureHeader, AUTH_SIG_HEADER}; +use crate::registry::RegistryDatabase; +use crate::rpc_continuations::RpcContinuations; +use crate::version::VersionT; + +#[derive(Debug, Clone, Default, Deserialize, Serialize, Parser)] +#[serde(rename_all = "kebab-case")] +#[command(rename_all = "kebab-case")] +pub struct RegistryConfig { + #[arg(short = 'c', long = "config")] + pub config: Option, + #[arg(short = 'l', long = "listen")] + pub listen: Option, + #[arg(short = 'h', long = "hostname")] + pub hostname: InternedString, + #[arg(short = 'd', long = "datadir")] + pub datadir: Option, +} +impl ContextConfig for RegistryConfig { + fn next(&mut self) -> Option { + self.config.take() + } + fn merge_with(&mut self, other: Self) { + self.datadir = self.datadir.take().or(other.datadir); + } +} + +impl RegistryConfig { + pub fn load(mut self) -> Result { + let path = self.next(); + self.load_path_rec(path)?; + self.load_path_rec(Some(CONFIG_PATH))?; + Ok(self) + } +} + +pub struct RegistryContextSeed { + pub hostname: InternedString, + pub listen: SocketAddr, + pub db: TypedPatchDb, + pub datadir: PathBuf, + pub rpc_continuations: RpcContinuations, + pub shutdown: Sender<()>, +} + +#[derive(Clone)] +pub struct RegistryContext(Arc); +impl RegistryContext { + #[instrument(skip_all)] + pub async fn init(config: &RegistryConfig) -> Result { + let (shutdown, _) = tokio::sync::broadcast::channel(1); + let datadir = config + .datadir + .as_deref() + .unwrap_or_else(|| Path::new("/var/lib/startos")) + .to_owned(); + if tokio::fs::metadata(&datadir).await.is_err() { + tokio::fs::create_dir_all(&datadir).await?; + } + let db_path = datadir.join("registry.db"); + let db = TypedPatchDb::::load_or_init( + PatchDb::open(&db_path).await?, + || async { Ok(Default::default()) }, + ) + .await?; + Ok(Self(Arc::new(RegistryContextSeed { + hostname: config.hostname.clone(), + listen: config + .listen + .unwrap_or(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 5959)), + db, + datadir, + rpc_continuations: RpcContinuations::new(), + shutdown, + }))) + } +} +impl AsRef for RegistryContext { + fn as_ref(&self) -> &RpcContinuations { + &self.rpc_continuations + } +} + +impl Context for RegistryContext {} +impl Deref for RegistryContext { + type Target = RegistryContextSeed; + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +#[derive(Debug, Deserialize, Serialize, Parser)] +pub struct RegistryUrlParams { + pub registry: Url, +} + +impl CallRemote for CliContext { + async fn call_remote( + &self, + mut method: &str, + params: Value, + _: Empty, + ) -> Result { + use reqwest::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE}; + use reqwest::Method; + use rpc_toolkit::yajrc::{GenericRpcMethod, Id, RpcRequest}; + use rpc_toolkit::RpcResponse; + + let url = self + .registry_url + .clone() + .ok_or_else(|| Error::new(eyre!("`--registry` required"), ErrorKind::InvalidRequest))?; + method = method.strip_prefix("registry.").unwrap_or(method); + + let rpc_req = RpcRequest { + id: Some(Id::Number(0.into())), + method: GenericRpcMethod::<_, _, Value>::new(method), + params, + }; + let body = serde_json::to_vec(&rpc_req)?; + let host = url.host().or_not_found("registry hostname")?.to_string(); + let res = self + .client + .request(Method::POST, url) + .header(CONTENT_TYPE, "application/json") + .header(ACCEPT, "application/json") + .header(CONTENT_LENGTH, body.len()) + .header( + AUTH_SIG_HEADER, + serde_urlencoded::to_string(&SignatureHeader::sign_ed25519( + self.developer_key()?, + &body, + &host, + )?) + .with_kind(ErrorKind::Serialization)?, + ) + .body(body) + .send() + .await?; + + match res + .headers() + .get(CONTENT_TYPE) + .and_then(|v| v.to_str().ok()) + { + Some("application/json") => { + serde_json::from_slice::(&*res.bytes().await?) + .with_kind(ErrorKind::Deserialization)? + .result + } + _ => Err(Error::new(eyre!("missing content type"), ErrorKind::Network).into()), + } + } +} + +fn hardware_header(ctx: &RpcContext) -> String { + let mut url: Url = "http://localhost".parse().unwrap(); + url.query_pairs_mut() + .append_pair( + "os.version", + &crate::version::Current::new().semver().to_string(), + ) + .append_pair( + "os.compat", + &crate::version::Current::new().compat().to_string(), + ) + .append_pair("os.arch", &*crate::PLATFORM) + .append_pair("hardware.arch", &*crate::ARCH) + .append_pair("hardware.ram", &ctx.hardware.ram.to_string()); + + for hw in &ctx.hardware.devices { + url.query_pairs_mut() + .append_pair(&format!("hardware.device.{}", hw.class()), hw.product()); + } + + url.query().unwrap_or_default().to_string() +} + +impl CallRemote for RpcContext { + async fn call_remote( + &self, + mut method: &str, + params: Value, + RegistryUrlParams { registry }: RegistryUrlParams, + ) -> Result { + use reqwest::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE}; + use reqwest::Method; + use rpc_toolkit::yajrc::{GenericRpcMethod, Id, RpcRequest}; + use rpc_toolkit::RpcResponse; + + let url = registry.join("rpc/v0")?; + method = method.strip_prefix("registry.").unwrap_or(method); + + let rpc_req = RpcRequest { + id: Some(Id::Number(0.into())), + method: GenericRpcMethod::<_, _, Value>::new(method), + params, + }; + let body = serde_json::to_vec(&rpc_req)?; + let res = self + .client + .request(Method::POST, url) + .header(CONTENT_TYPE, "application/json") + .header(ACCEPT, "application/json") + .header(CONTENT_LENGTH, body.len()) + .header("X-StartOS-Hardware", &hardware_header(self)) + .body(body) + .send() + .await?; + + match res + .headers() + .get(CONTENT_TYPE) + .and_then(|v| v.to_str().ok()) + { + Some("application/json") => { + serde_json::from_slice::(&*res.bytes().await?) + .with_kind(ErrorKind::Deserialization)? + .result + } + _ => Err(Error::new(eyre!("missing content type"), ErrorKind::Network).into()), + } + } +} diff --git a/core/startos/src/registry/db.rs b/core/startos/src/registry/db.rs new file mode 100644 index 000000000..df39604f1 --- /dev/null +++ b/core/startos/src/registry/db.rs @@ -0,0 +1,171 @@ +use std::path::PathBuf; + +use clap::Parser; +use itertools::Itertools; +use patch_db::json_ptr::{JsonPointer, ROOT}; +use patch_db::Dump; +use rpc_toolkit::yajrc::RpcError; +use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use tracing::instrument; +use ts_rs::TS; + +use crate::context::CliContext; +use crate::prelude::*; +use crate::registry::context::RegistryContext; +use crate::registry::RegistryDatabase; +use crate::util::serde::{apply_expr, HandlerExtSerde}; + +pub fn db_api() -> ParentHandler { + ParentHandler::new() + .subcommand("dump", from_fn_async(cli_dump).with_display_serializable()) + .subcommand( + "dump", + from_fn_async(dump) + .with_metadata("admin", Value::Bool(true)) + .no_cli(), + ) + .subcommand("apply", from_fn_async(cli_apply).no_display()) + .subcommand( + "apply", + from_fn_async(apply) + .with_metadata("admin", Value::Bool(true)) + .no_cli(), + ) +} + +#[derive(Deserialize, Serialize, Parser)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct CliDumpParams { + #[arg(long = "pointer", short = 'p')] + pointer: Option, + path: Option, +} + +#[instrument(skip_all)] +async fn cli_dump( + HandlerArgs { + context, + parent_method, + method, + params: CliDumpParams { pointer, path }, + .. + }: HandlerArgs, +) -> Result { + let dump = if let Some(path) = path { + PatchDb::open(path).await?.dump(&ROOT).await + } else { + let method = parent_method.into_iter().chain(method).join("."); + from_value::( + context + .call_remote::(&method, imbl_value::json!({ "pointer": pointer })) + .await?, + )? + }; + + Ok(dump) +} + +#[derive(Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct DumpParams { + #[arg(long = "pointer", short = 'p')] + #[ts(type = "string | null")] + pointer: Option, +} + +pub async fn dump(ctx: RegistryContext, DumpParams { pointer }: DumpParams) -> Result { + Ok(ctx + .db + .dump(&pointer.as_ref().map_or(ROOT, |p| p.borrowed())) + .await) +} + +#[derive(Deserialize, Serialize, Parser)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct CliApplyParams { + expr: String, + path: Option, +} + +#[instrument(skip_all)] +async fn cli_apply( + HandlerArgs { + context, + parent_method, + method, + params: CliApplyParams { expr, path }, + .. + }: HandlerArgs, +) -> Result<(), RpcError> { + if let Some(path) = path { + PatchDb::open(path) + .await? + .apply_function(|db| { + let res = apply_expr( + serde_json::to_value(patch_db::Value::from(db)) + .with_kind(ErrorKind::Deserialization)? + .into(), + &expr, + )?; + + Ok::<_, Error>(( + to_value( + &serde_json::from_value::(res.clone().into()).with_ctx( + |_| { + ( + crate::ErrorKind::Deserialization, + "result does not match database model", + ) + }, + )?, + )?, + (), + )) + }) + .await?; + } else { + let method = parent_method.into_iter().chain(method).join("."); + context + .call_remote::(&method, imbl_value::json!({ "expr": expr })) + .await?; + } + + Ok(()) +} + +#[derive(Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] +#[command(rename_all = "kebab-case")] +pub struct ApplyParams { + expr: String, + path: Option, +} + +pub async fn apply( + ctx: RegistryContext, + ApplyParams { expr, .. }: ApplyParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + let res = apply_expr( + serde_json::to_value(patch_db::Value::from(db.clone())) + .with_kind(ErrorKind::Deserialization)? + .into(), + &expr, + )?; + + db.ser( + &serde_json::from_value::(res.clone().into()).with_ctx(|_| { + ( + crate::ErrorKind::Deserialization, + "result does not match database model", + ) + })?, + ) + }) + .await +} diff --git a/core/startos/src/registry/marketplace.rs b/core/startos/src/registry/marketplace.rs deleted file mode 100644 index 82ead81cb..000000000 --- a/core/startos/src/registry/marketplace.rs +++ /dev/null @@ -1,101 +0,0 @@ -use base64::Engine; -use clap::Parser; -use color_eyre::eyre::eyre; -use reqwest::{StatusCode, Url}; -use rpc_toolkit::{command, from_fn_async, HandlerExt, ParentHandler}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use ts_rs::TS; - -use crate::context::{CliContext, RpcContext}; -use crate::version::VersionT; -use crate::{Error, ResultExt}; - -pub fn marketplace() -> ParentHandler { - ParentHandler::new().subcommand("get", from_fn_async(get).with_remote_cli::()) -} - -pub fn with_query_params(ctx: RpcContext, mut url: Url) -> Url { - url.query_pairs_mut() - .append_pair( - "os.version", - &crate::version::Current::new().semver().to_string(), - ) - .append_pair( - "os.compat", - &crate::version::Current::new().compat().to_string(), - ) - .append_pair("os.arch", &*crate::PLATFORM) - .append_pair("hardware.arch", &*crate::ARCH) - .append_pair("hardware.ram", &ctx.hardware.ram.to_string()); - - for hw in &ctx.hardware.devices { - url.query_pairs_mut() - .append_pair(&format!("hardware.device.{}", hw.class()), hw.product()); - } - - url -} - -#[derive(Deserialize, Serialize, Parser, TS)] -#[serde(rename_all = "camelCase")] -#[command(rename_all = "kebab-case")] -pub struct GetParams { - #[ts(type = "string")] - url: Url, -} - -pub async fn get(ctx: RpcContext, GetParams { url }: GetParams) -> Result { - let mut response = ctx - .client - .get(with_query_params(ctx.clone(), url)) - .send() - .await - .with_kind(crate::ErrorKind::Network)?; - let status = response.status(); - if status.is_success() { - match response - .headers_mut() - .remove("Content-Type") - .as_ref() - .and_then(|h| h.to_str().ok()) - .and_then(|h| h.split(";").next()) - .map(|h| h.trim()) - { - Some("application/json") => response - .json() - .await - .with_kind(crate::ErrorKind::Deserialization), - Some("text/plain") => Ok(Value::String( - response - .text() - .await - .with_kind(crate::ErrorKind::Registry)?, - )), - Some(ctype) => Ok(Value::String(format!( - "data:{};base64,{}", - ctype, - base64::engine::general_purpose::URL_SAFE.encode( - &response - .bytes() - .await - .with_kind(crate::ErrorKind::Registry)? - ) - ))), - _ => Err(Error::new( - eyre!("missing Content-Type"), - crate::ErrorKind::Registry, - )), - } - } else { - let message = response.text().await.with_kind(crate::ErrorKind::Network)?; - Err(Error::new( - eyre!("{}", message), - match status { - StatusCode::BAD_REQUEST => crate::ErrorKind::InvalidRequest, - StatusCode::NOT_FOUND => crate::ErrorKind::NotFound, - _ => crate::ErrorKind::Registry, - }, - )) - } -} diff --git a/core/startos/src/registry/mod.rs b/core/startos/src/registry/mod.rs index 27f541f1d..c063e5823 100644 --- a/core/startos/src/registry/mod.rs +++ b/core/startos/src/registry/mod.rs @@ -1,2 +1,126 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::net::SocketAddr; + +use axum::Router; +use futures::future::ready; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler, Server}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +use crate::context::{CliContext}; +use crate::middleware::cors::Cors; +use crate::net::static_server::{bad_request, not_found, server_error}; +use crate::net::web_server::WebServer; +use crate::prelude::*; +use crate::registry::auth::Auth; +use crate::registry::context::{RegistryContext}; +use crate::registry::os::index::OsIndex; +use crate::registry::signer::SignerInfo; +use crate::rpc_continuations::RequestGuid; +use crate::util::serde::HandlerExtSerde; + pub mod admin; -pub mod marketplace; +pub mod asset; +pub mod auth; +pub mod context; +pub mod db; +pub mod os; +pub mod signer; + +#[derive(Debug, Default, Deserialize, Serialize, HasModel)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +pub struct RegistryDatabase { + pub admins: BTreeSet, + pub index: FullIndex, +} + +#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +#[ts(export)] +pub struct FullIndex { + // pub package: PackageIndex, + pub os: OsIndex, + #[ts(as = "BTreeMap::")] + pub signers: BTreeMap, +} + +pub async fn get_full_index(ctx: RegistryContext) -> Result { + ctx.db.peek().await.into_index().de() +} + +pub fn registry_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "index", + from_fn_async(get_full_index) + .with_display_serializable() + .with_call_remote::(), + ) + .subcommand("os", os::os_api::()) + .subcommand("admin", admin::admin_api::()) + .subcommand("db", db::db_api::()) +} + +pub fn registry_server_router(ctx: RegistryContext) -> Router { + use axum::extract as x; + use axum::routing::{any, get, post}; + Router::new() + .route("/rpc/*path", { + let ctx = ctx.clone(); + post( + Server::new(move || ready(Ok(ctx.clone())), registry_api()) + .middleware(Cors::new()) + .middleware(Auth::new()), + ) + }) + .route( + "/ws/rpc/*path", + get({ + let ctx = ctx.clone(); + move |x::Path(path): x::Path, + ws: axum::extract::ws::WebSocketUpgrade| async move { + match RequestGuid::from(&path) { + None => { + tracing::debug!("No Guid Path"); + bad_request() + } + Some(guid) => match ctx.rpc_continuations.get_ws_handler(&guid).await { + Some(cont) => ws.on_upgrade(cont), + _ => not_found(), + }, + } + } + }), + ) + .route( + "/rest/rpc/*path", + any({ + let ctx = ctx.clone(); + move |request: x::Request| async move { + let path = request + .uri() + .path() + .strip_prefix("/rest/rpc/") + .unwrap_or_default(); + match RequestGuid::from(&path) { + None => { + tracing::debug!("No Guid Path"); + bad_request() + } + Some(guid) => match ctx.rpc_continuations.get_rest_handler(&guid).await { + None => not_found(), + Some(cont) => cont(request).await.unwrap_or_else(server_error), + }, + } + } + }), + ) +} + +impl WebServer { + pub fn registry(bind: SocketAddr, ctx: RegistryContext) -> Self { + Self::new(bind, registry_server_router(ctx)) + } +} diff --git a/core/startos/src/registry/os/asset/add.rs b/core/startos/src/registry/os/asset/add.rs new file mode 100644 index 000000000..6e259e314 --- /dev/null +++ b/core/startos/src/registry/os/asset/add.rs @@ -0,0 +1,341 @@ +use std::collections::BTreeMap; +use std::panic::UnwindSafe; +use std::path::PathBuf; +use std::time::Duration; + +use axum::response::Response; +use clap::Parser; +use futures::{FutureExt, TryStreamExt}; +use helpers::NonDetachingJoinHandle; +use imbl_value::InternedString; +use itertools::Itertools; +use rpc_toolkit::{from_fn_async, CallRemote, Context, HandlerArgs, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha512}; +use ts_rs::TS; +use url::Url; + +use crate::context::CliContext; +use crate::prelude::*; +use crate::progress::{FullProgressTracker, PhasedProgressBar}; +use crate::registry::asset::RegistryAsset; +use crate::registry::context::RegistryContext; +use crate::registry::os::index::OsVersionInfo; +use crate::registry::os::SIG_CONTEXT; +use crate::registry::signer::{Blake3Ed25519Signature, Signature, SignatureInfo, SignerKey}; +use crate::rpc_continuations::{RequestGuid, RpcContinuation}; +use crate::s9pk::merkle_archive::source::ArchiveSource; +use crate::util::{Apply, Version}; + +pub fn add_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "iso", + from_fn_async(add_iso) + .with_metadata("getSigner", Value::Bool(true)) + .no_cli(), + ) + .subcommand( + "img", + from_fn_async(add_img) + .with_metadata("getSigner", Value::Bool(true)) + .no_cli(), + ) + .subcommand( + "squashfs", + from_fn_async(add_squashfs) + .with_metadata("getSigner", Value::Bool(true)) + .no_cli(), + ) +} + +#[derive(Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct AddAssetParams { + #[ts(type = "string")] + pub url: Url, + pub signature: Signature, + #[ts(type = "string")] + pub version: Version, + #[ts(type = "string")] + pub platform: InternedString, + #[serde(default)] + pub upload: bool, + #[serde(rename = "__auth_signer")] + pub signer: SignerKey, +} + +async fn add_asset( + ctx: RegistryContext, + AddAssetParams { + url, + signature, + version, + platform, + upload, + signer, + }: AddAssetParams, + accessor: impl FnOnce(&mut Model) -> &mut Model> + + UnwindSafe + + Send, +) -> Result, Error> { + ensure_code!( + signature.signer() == signer, + ErrorKind::InvalidSignature, + "asset signature does not match request signer" + ); + + ctx.db + .mutate(|db| { + let signer_guid = db.as_index().as_signers().get_signer(&signer)?; + if db + .as_index() + .as_os() + .as_versions() + .as_idx(&version) + .or_not_found(&version)? + .as_signers() + .de()? + .contains(&signer_guid) + { + accessor( + db.as_index_mut() + .as_os_mut() + .as_versions_mut() + .as_idx_mut(&version) + .or_not_found(&version)?, + ) + .upsert(&platform, || RegistryAsset { + url, + signature_info: SignatureInfo::new(SIG_CONTEXT), + })? + .as_signature_info_mut() + .mutate(|s| s.add_sig(&signature))?; + Ok(()) + } else { + Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization)) + } + }) + .await?; + + let guid = if upload { + let guid = RequestGuid::new(); + let auth_guid = guid.clone(); + let signer = signature.signer(); + let hostname = ctx.hostname.clone(); + ctx.rpc_continuations + .add( + guid.clone(), + RpcContinuation::rest( + Box::new(|req| { + async move { + Ok( + if async move { + let auth_sig = base64::decode( + req.headers().get("X-StartOS-Registry-Auth-Sig")?, + ) + .ok()?; + signer + .verify_message( + auth_guid.as_ref().as_bytes(), + &auth_sig, + &hostname, + ) + .ok()?; + + Some(()) + } + .await + .is_some() + { + Response::builder() + .status(200) + .body(axum::body::Body::empty()) + .with_kind(ErrorKind::Network)? + } else { + Response::builder() + .status(401) + .body(axum::body::Body::empty()) + .with_kind(ErrorKind::Network)? + }, + ) + } + .boxed() + }), + Duration::from_secs(30), + ), + ) + .await; + Some(guid) + } else { + None + }; + + Ok(guid) +} + +pub async fn add_iso( + ctx: RegistryContext, + params: AddAssetParams, +) -> Result, Error> { + add_asset(ctx, params, |m| m.as_iso_mut()).await +} + +pub async fn add_img( + ctx: RegistryContext, + params: AddAssetParams, +) -> Result, Error> { + add_asset(ctx, params, |m| m.as_img_mut()).await +} + +pub async fn add_squashfs( + ctx: RegistryContext, + params: AddAssetParams, +) -> Result, Error> { + add_asset(ctx, params, |m| m.as_squashfs_mut()).await +} + +#[derive(Debug, Deserialize, Serialize, Parser)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +pub struct CliAddAssetParams { + #[arg(short = 'p', long = "platform")] + pub platform: InternedString, + #[arg(short = 'v', long = "version")] + pub version: Version, + pub file: PathBuf, + pub url: Url, + #[arg(short = 'u', long = "upload")] + pub upload: bool, +} + +pub async fn cli_add_asset( + HandlerArgs { + context: ctx, + parent_method, + method, + params: + CliAddAssetParams { + platform, + version, + file: path, + url, + upload, + }, + .. + }: HandlerArgs, +) -> Result<(), Error> { + let ext = match path.extension().and_then(|e| e.to_str()) { + Some("iso") => "iso", + Some("img") => "img", + Some("squashfs") => "squashfs", + _ => { + return Err(Error::new( + eyre!("Unknown extension"), + ErrorKind::InvalidRequest, + )) + } + }; + + let file = tokio::fs::File::open(&path).await?.into(); + + let mut progress = FullProgressTracker::new(); + let progress_handle = progress.handle(); + let mut sign_phase = + progress_handle.add_phase(InternedString::intern("Signing File"), Some(10)); + let mut index_phase = progress_handle.add_phase( + InternedString::intern("Adding File to Registry Index"), + Some(1), + ); + let mut upload_phase = if upload { + Some(progress_handle.add_phase(InternedString::intern("Uploading File"), Some(100))) + } else { + None + }; + + let progress_task: NonDetachingJoinHandle<()> = tokio::spawn(async move { + let mut bar = PhasedProgressBar::new(&format!("Adding {} to registry...", path.display())); + loop { + let snap = progress.snapshot(); + bar.update(&snap); + if snap.overall.is_complete() { + break; + } + progress.changed().await + } + }) + .into(); + + sign_phase.start(); + let blake3_sig = + Blake3Ed25519Signature::sign_file(ctx.developer_key()?, &file, SIG_CONTEXT).await?; + let size = blake3_sig.size; + let signature = Signature::Blake3Ed25519(blake3_sig); + sign_phase.complete(); + + index_phase.start(); + let add_res = from_value::>( + ctx.call_remote::( + &parent_method + .into_iter() + .chain(method) + .chain([ext]) + .join("."), + imbl_value::json!({ + "platform": platform, + "version": version, + "url": &url, + "signature": signature, + "upload": upload, + }), + ) + .await?, + )?; + index_phase.complete(); + + if let Some(guid) = add_res { + upload_phase.as_mut().map(|p| p.start()); + upload_phase.as_mut().map(|p| p.set_total(size)); + let reg_url = ctx.registry_url.as_ref().or_not_found("--registry")?; + ctx.client + .post(url) + .header("X-StartOS-Registry-Token", guid.as_ref()) + .header( + "X-StartOS-Registry-Auth-Sig", + base64::encode( + ctx.developer_key()? + .sign_prehashed( + Sha512::new_with_prefix(guid.as_ref().as_bytes()), + Some( + reg_url + .host() + .or_not_found("registry hostname")? + .to_string() + .as_bytes(), + ), + )? + .to_bytes(), + ), + ) + .body(reqwest::Body::wrap_stream( + tokio_util::io::ReaderStream::new(file.fetch(0, size).await?).inspect_ok( + move |b| { + upload_phase + .as_mut() + .map(|p| *p += b.len() as u64) + .apply(|_| ()) + }, + ), + )) + .send() + .await?; + // upload_phase.as_mut().map(|p| p.complete()); + } + + progress_handle.complete(); + + progress_task.await.with_kind(ErrorKind::Unknown)?; + + Ok(()) +} diff --git a/core/startos/src/registry/os/asset/get.rs b/core/startos/src/registry/os/asset/get.rs new file mode 100644 index 000000000..52e95bc98 --- /dev/null +++ b/core/startos/src/registry/os/asset/get.rs @@ -0,0 +1,182 @@ +use std::collections::BTreeMap; +use std::panic::UnwindSafe; +use std::path::{Path, PathBuf}; + +use clap::Parser; +use helpers::{AtomicFile, NonDetachingJoinHandle}; +use imbl_value::{json, InternedString}; +use itertools::Itertools; +use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +use crate::context::CliContext; +use crate::prelude::*; +use crate::progress::{FullProgressTracker, PhasedProgressBar}; +use crate::registry::asset::RegistryAsset; +use crate::registry::context::RegistryContext; +use crate::registry::os::index::OsVersionInfo; +use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; +use crate::util::Version; + +pub fn get_api() -> ParentHandler { + ParentHandler::new() + .subcommand("iso", from_fn_async(get_iso).no_cli()) + .subcommand("iso", from_fn_async(cli_get_os_asset).no_display()) + .subcommand("img", from_fn_async(get_img).no_cli()) + .subcommand("img", from_fn_async(cli_get_os_asset).no_display()) + .subcommand("squashfs", from_fn_async(get_squashfs).no_cli()) + .subcommand("squashfs", from_fn_async(cli_get_os_asset).no_display()) +} + +#[derive(Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct GetOsAssetParams { + #[ts(type = "string")] + pub version: Version, + #[ts(type = "string")] + pub platform: InternedString, +} + +async fn get_os_asset( + ctx: RegistryContext, + GetOsAssetParams { version, platform }: GetOsAssetParams, + accessor: impl FnOnce(&Model) -> &Model> + + UnwindSafe + + Send, +) -> Result { + accessor( + ctx.db + .peek() + .await + .as_index() + .as_os() + .as_versions() + .as_idx(&version) + .or_not_found(&version)?, + ) + .as_idx(&platform) + .or_not_found(&platform)? + .de() +} + +pub async fn get_iso( + ctx: RegistryContext, + params: GetOsAssetParams, +) -> Result { + get_os_asset(ctx, params, |info| info.as_iso()).await +} + +pub async fn get_img( + ctx: RegistryContext, + params: GetOsAssetParams, +) -> Result { + get_os_asset(ctx, params, |info| info.as_img()).await +} + +pub async fn get_squashfs( + ctx: RegistryContext, + params: GetOsAssetParams, +) -> Result { + get_os_asset(ctx, params, |info| info.as_squashfs()).await +} + +#[derive(Debug, Deserialize, Serialize, Parser)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +pub struct CliGetOsAssetParams { + pub version: Version, + pub platform: InternedString, + #[arg(long = "download", short = 'd')] + pub download: Option, + #[arg( + long = "reverify", + short = 'r', + help = "verify the hash of the file a second time after download" + )] + pub reverify: bool, +} + +async fn cli_get_os_asset( + HandlerArgs { + context: ctx, + parent_method, + method, + params: + CliGetOsAssetParams { + version, + platform, + download, + reverify, + }, + .. + }: HandlerArgs, +) -> Result { + let res = from_value::( + ctx.call_remote::( + &parent_method.into_iter().chain(method).join("."), + json!({ + "version": version, + "platform": platform, + }), + ) + .await?, + )?; + + let validator = res.validate(res.signature_info.all_signers())?; + + if let Some(download) = download { + let mut file = AtomicFile::new(&download, None::<&Path>) + .await + .with_kind(ErrorKind::Filesystem)?; + + let mut progress = FullProgressTracker::new(); + let progress_handle = progress.handle(); + let mut download_phase = + progress_handle.add_phase(InternedString::intern("Downloading File"), Some(100)); + download_phase.set_total(validator.size()?); + let reverify_phase = if reverify { + Some(progress_handle.add_phase(InternedString::intern("Reverifying File"), Some(10))) + } else { + None + }; + + let progress_task: NonDetachingJoinHandle<()> = tokio::spawn(async move { + let mut bar = PhasedProgressBar::new("Downloading..."); + loop { + let snap = progress.snapshot(); + bar.update(&snap); + if snap.overall.is_complete() { + break; + } + progress.changed().await + } + }) + .into(); + + download_phase.start(); + let mut download_writer = download_phase.writer(&mut *file); + res.download(ctx.client.clone(), &mut download_writer, &validator) + .await?; + let (_, mut download_phase) = download_writer.into_inner(); + file.save().await.with_kind(ErrorKind::Filesystem)?; + download_phase.complete(); + + if let Some(mut reverify_phase) = reverify_phase { + reverify_phase.start(); + validator + .validate_file(&MultiCursorFile::from( + tokio::fs::File::open(download).await?, + )) + .await?; + reverify_phase.complete(); + } + + progress_handle.complete(); + + progress_task.await.with_kind(ErrorKind::Unknown)?; + } + + Ok(res) +} diff --git a/core/startos/src/registry/os/asset/mod.rs b/core/startos/src/registry/os/asset/mod.rs new file mode 100644 index 000000000..ec9d6cae7 --- /dev/null +++ b/core/startos/src/registry/os/asset/mod.rs @@ -0,0 +1,14 @@ +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; + +pub mod add; +pub mod get; +pub mod sign; + +pub fn asset_api() -> ParentHandler { + ParentHandler::new() + .subcommand("add", add::add_api::()) + .subcommand("add", from_fn_async(add::cli_add_asset).no_display()) + .subcommand("sign", sign::sign_api::()) + .subcommand("sign", from_fn_async(sign::cli_sign_asset).no_display()) + .subcommand("get", get::get_api::()) +} diff --git a/core/startos/src/registry/os/asset/sign.rs b/core/startos/src/registry/os/asset/sign.rs new file mode 100644 index 000000000..e19a82899 --- /dev/null +++ b/core/startos/src/registry/os/asset/sign.rs @@ -0,0 +1,188 @@ +use std::collections::BTreeMap; +use std::panic::UnwindSafe; +use std::path::PathBuf; + +use clap::Parser; +use helpers::NonDetachingJoinHandle; +use imbl_value::InternedString; +use itertools::Itertools; +use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +use crate::context::CliContext; +use crate::prelude::*; +use crate::progress::{FullProgressTracker, PhasedProgressBar}; +use crate::registry::asset::RegistryAsset; +use crate::registry::context::RegistryContext; +use crate::registry::os::index::OsVersionInfo; +use crate::registry::os::SIG_CONTEXT; +use crate::registry::signer::{Blake3Ed25519Signature, Signature}; +use crate::util::Version; + +pub fn sign_api() -> ParentHandler { + ParentHandler::new() + .subcommand("iso", from_fn_async(sign_iso).no_cli()) + .subcommand("img", from_fn_async(sign_img).no_cli()) + .subcommand("squashfs", from_fn_async(sign_squashfs).no_cli()) +} + +#[derive(Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct SignAssetParams { + #[ts(type = "string")] + version: Version, + #[ts(type = "string")] + platform: InternedString, + signature: Signature, +} + +async fn sign_asset( + ctx: RegistryContext, + SignAssetParams { + version, + platform, + signature, + }: SignAssetParams, + accessor: impl FnOnce(&mut Model) -> &mut Model> + + UnwindSafe + + Send, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + let guid = db.as_index().as_signers().get_signer(&signature.signer())?; + if !db + .as_index() + .as_os() + .as_versions() + .as_idx(&version) + .or_not_found(&version)? + .as_signers() + .de()? + .contains(&guid) + { + return Err(Error::new( + eyre!("signer {guid} is not authorized"), + ErrorKind::Authorization, + )); + } + + accessor( + db.as_index_mut() + .as_os_mut() + .as_versions_mut() + .as_idx_mut(&version) + .or_not_found(&version)?, + ) + .as_idx_mut(&platform) + .or_not_found(&platform)? + .as_signature_info_mut() + .mutate(|s| s.add_sig(&signature))?; + + Ok(()) + }) + .await +} + +pub async fn sign_iso(ctx: RegistryContext, params: SignAssetParams) -> Result<(), Error> { + sign_asset(ctx, params, |m| m.as_iso_mut()).await +} + +pub async fn sign_img(ctx: RegistryContext, params: SignAssetParams) -> Result<(), Error> { + sign_asset(ctx, params, |m| m.as_img_mut()).await +} + +pub async fn sign_squashfs(ctx: RegistryContext, params: SignAssetParams) -> Result<(), Error> { + sign_asset(ctx, params, |m| m.as_squashfs_mut()).await +} + +#[derive(Debug, Deserialize, Serialize, Parser)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +pub struct CliSignAssetParams { + #[arg(short = 'p', long = "platform")] + pub platform: InternedString, + #[arg(short = 'v', long = "version")] + pub version: Version, + pub file: PathBuf, +} + +pub async fn cli_sign_asset( + HandlerArgs { + context: ctx, + parent_method, + method, + params: + CliSignAssetParams { + platform, + version, + file: path, + }, + .. + }: HandlerArgs, +) -> Result<(), Error> { + let ext = match path.extension().and_then(|e| e.to_str()) { + Some("iso") => "iso", + Some("img") => "img", + Some("squashfs") => "squashfs", + _ => { + return Err(Error::new( + eyre!("Unknown extension"), + ErrorKind::InvalidRequest, + )) + } + }; + + let file = tokio::fs::File::open(&path).await?.into(); + + let mut progress = FullProgressTracker::new(); + let progress_handle = progress.handle(); + let mut sign_phase = + progress_handle.add_phase(InternedString::intern("Signing File"), Some(10)); + let mut index_phase = progress_handle.add_phase( + InternedString::intern("Adding Signature to Registry Index"), + Some(1), + ); + + let progress_task: NonDetachingJoinHandle<()> = tokio::spawn(async move { + let mut bar = PhasedProgressBar::new(&format!("Adding {} to registry...", path.display())); + loop { + let snap = progress.snapshot(); + bar.update(&snap); + if snap.overall.is_complete() { + break; + } + progress.changed().await + } + }) + .into(); + + sign_phase.start(); + let blake3_sig = + Blake3Ed25519Signature::sign_file(ctx.developer_key()?, &file, SIG_CONTEXT).await?; + let signature = Signature::Blake3Ed25519(blake3_sig); + sign_phase.complete(); + + index_phase.start(); + ctx.call_remote::( + &parent_method + .into_iter() + .chain(method) + .chain([ext]) + .join("."), + imbl_value::json!({ + "platform": platform, + "version": version, + "signature": signature, + }), + ) + .await?; + index_phase.complete(); + + progress_handle.complete(); + + progress_task.await.with_kind(ErrorKind::Unknown)?; + + Ok(()) +} diff --git a/core/startos/src/registry/os/index.rs b/core/startos/src/registry/os/index.rs new file mode 100644 index 000000000..186a7e95e --- /dev/null +++ b/core/startos/src/registry/os/index.rs @@ -0,0 +1,44 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use emver::VersionRange; +use imbl_value::InternedString; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +use crate::prelude::*; +use crate::registry::asset::RegistryAsset; +use crate::registry::context::RegistryContext; +use crate::rpc_continuations::RequestGuid; +use crate::util::Version; + +#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +#[ts(export)] +pub struct OsIndex { + #[ts(as = "BTreeMap::")] + pub versions: BTreeMap, +} + +#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +#[ts(export)] +pub struct OsVersionInfo { + pub headline: String, + pub release_notes: String, + #[ts(type = "string")] + pub source_version: VersionRange, + #[ts(type = "string[]")] + pub signers: BTreeSet, + #[ts(as = "BTreeMap::")] + pub iso: BTreeMap, // platform (i.e. x86_64-nonfree) -> asset + #[ts(as = "BTreeMap::")] + pub squashfs: BTreeMap, // platform (i.e. x86_64-nonfree) -> asset + #[ts(as = "BTreeMap::")] + pub img: BTreeMap, // platform (i.e. raspberrypi) -> asset +} + +pub async fn get_os_index(ctx: RegistryContext) -> Result { + ctx.db.peek().await.into_index().into_os().de() +} diff --git a/core/startos/src/registry/os/mod.rs b/core/startos/src/registry/os/mod.rs new file mode 100644 index 000000000..64ce44eaf --- /dev/null +++ b/core/startos/src/registry/os/mod.rs @@ -0,0 +1,22 @@ +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; + +use crate::context::CliContext; +use crate::util::serde::HandlerExtSerde; + +pub const SIG_CONTEXT: &str = "startos"; + +pub mod asset; +pub mod index; +pub mod version; + +pub fn os_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "index", + from_fn_async(index::get_os_index) + .with_display_serializable() + .with_call_remote::(), + ) + .subcommand("asset", asset::asset_api::()) + .subcommand("version", version::version_api::()) +} diff --git a/core/startos/src/registry/os/version/mod.rs b/core/startos/src/registry/os/version/mod.rs new file mode 100644 index 000000000..414994875 --- /dev/null +++ b/core/startos/src/registry/os/version/mod.rs @@ -0,0 +1,183 @@ +use std::collections::BTreeMap; + +use clap::Parser; +use emver::VersionRange; +use itertools::Itertools; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +use crate::context::CliContext; +use crate::prelude::*; +use crate::registry::context::RegistryContext; +use crate::registry::os::index::OsVersionInfo; +use crate::registry::signer::SignerKey; +use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; +use crate::util::Version; + +pub mod signer; + +pub fn version_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "add", + from_fn_async(add_version) + .with_metadata("admin", Value::Bool(true)) + .with_metadata("getSigner", Value::Bool(true)) + .no_display() + .with_call_remote::(), + ) + .subcommand( + "remove", + from_fn_async(remove_version) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_call_remote::(), + ) + .subcommand("signer", signer::signer_api::()) + .subcommand( + "get", + from_fn_async(get_version) + .with_display_serializable() + .with_custom_display_fn(|handle, result| { + Ok(display_version_info(handle.params, result)) + }) + .with_call_remote::(), + ) +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct AddVersionParams { + #[ts(type = "string")] + pub version: Version, + pub headline: String, + pub release_notes: String, + #[ts(type = "string")] + pub source_version: VersionRange, + #[arg(skip)] + #[ts(skip)] + #[serde(rename = "__auth_signer")] + pub signer: Option, +} + +pub async fn add_version( + ctx: RegistryContext, + AddVersionParams { + version, + headline, + release_notes, + source_version, + signer, + }: AddVersionParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + let signer = signer + .map(|s| db.as_index().as_signers().get_signer(&s)) + .transpose()?; + db.as_index_mut() + .as_os_mut() + .as_versions_mut() + .upsert(&version, || OsVersionInfo::default())? + .mutate(|i| { + i.headline = headline; + i.release_notes = release_notes; + i.source_version = source_version; + i.signers.extend(signer); + Ok(()) + }) + }) + .await +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct RemoveVersionParams { + #[ts(type = "string")] + pub version: Version, +} + +pub async fn remove_version( + ctx: RegistryContext, + RemoveVersionParams { version }: RemoveVersionParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + db.as_index_mut() + .as_os_mut() + .as_versions_mut() + .remove(&version)?; + Ok(()) + }) + .await +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct GetVersionParams { + #[ts(type = "string | null")] + #[arg(long = "src")] + pub source: Option, + #[ts(type = "string | null")] + #[arg(long = "target")] + pub target: Option, +} + +pub async fn get_version( + ctx: RegistryContext, + GetVersionParams { source, target }: GetVersionParams, +) -> Result, Error> { + let target = target.unwrap_or(VersionRange::Any); + ctx.db + .peek() + .await + .into_index() + .into_os() + .into_versions() + .into_entries()? + .into_iter() + .map(|(v, i)| i.de().map(|i| (v, i))) + .filter_ok(|(version, info)| { + version.satisfies(&target) + && source + .as_ref() + .map_or(true, |s| s.satisfies(&info.source_version)) + }) + .collect() +} + +pub fn display_version_info(params: WithIoFormat, info: BTreeMap) { + use prettytable::*; + + if let Some(format) = params.format { + return display_serializable(format, info); + } + + let mut table = Table::new(); + table.add_row(row![bc => + "VERSION", + "HEADLINE", + "RELEASE NOTES", + "ISO PLATFORMS", + "IMG PLATFORMS", + "SQUASHFS PLATFORMS", + ]); + for (version, info) in &info { + table.add_row(row![ + version.as_str(), + &info.headline, + &info.release_notes, + &info.iso.keys().into_iter().join(", "), + &info.img.keys().into_iter().join(", "), + &info.squashfs.keys().into_iter().join(", "), + ]); + } + table.print_tty(false).unwrap(); +} diff --git a/core/startos/src/registry/os/version/signer.rs b/core/startos/src/registry/os/version/signer.rs new file mode 100644 index 000000000..01173ad18 --- /dev/null +++ b/core/startos/src/registry/os/version/signer.rs @@ -0,0 +1,133 @@ +use std::collections::BTreeMap; + +use clap::Parser; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +use crate::context::CliContext; +use crate::prelude::*; +use crate::registry::admin::display_signers; +use crate::registry::context::RegistryContext; +use crate::registry::signer::SignerInfo; +use crate::rpc_continuations::RequestGuid; +use crate::util::serde::HandlerExtSerde; +use crate::util::Version; + +pub fn signer_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "add", + from_fn_async(add_version_signer) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_call_remote::(), + ) + .subcommand( + "remove", + from_fn_async(remove_version_signer) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_call_remote::(), + ) + .subcommand( + "list", + from_fn_async(list_version_signers) + .with_display_serializable() + .with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result))) + .with_call_remote::(), + ) +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct VersionSignerParams { + #[ts(type = "string")] + pub version: Version, + #[ts(type = "string")] + pub signer: RequestGuid, +} + +pub async fn add_version_signer( + ctx: RegistryContext, + VersionSignerParams { version, signer }: VersionSignerParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + ensure_code!( + db.as_index().as_signers().contains_key(&signer)?, + ErrorKind::InvalidRequest, + "unknown signer {signer}" + ); + + db.as_index_mut() + .as_os_mut() + .as_versions_mut() + .as_idx_mut(&version) + .or_not_found(&version)? + .as_signers_mut() + .mutate(|s| Ok(s.insert(signer)))?; + + Ok(()) + }) + .await +} + +pub async fn remove_version_signer( + ctx: RegistryContext, + VersionSignerParams { version, signer }: VersionSignerParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + if !db + .as_index_mut() + .as_os_mut() + .as_versions_mut() + .as_idx_mut(&version) + .or_not_found(&version)? + .as_signers_mut() + .mutate(|s| Ok(s.remove(&signer)))? + { + return Err(Error::new( + eyre!("signer {signer} is not authorized to sign for v{version}"), + ErrorKind::NotFound, + )); + } + + Ok(()) + }) + .await +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct ListVersionSignersParams { + #[ts(type = "string")] + pub version: Version, +} + +pub async fn list_version_signers( + ctx: RegistryContext, + ListVersionSignersParams { version }: ListVersionSignersParams, +) -> Result, Error> { + let db = ctx.db.peek().await; + db.as_index() + .as_os() + .as_versions() + .as_idx(&version) + .or_not_found(&version)? + .as_signers() + .de()? + .into_iter() + .filter_map(|guid| { + db.as_index() + .as_signers() + .as_idx(&guid) + .map(|s| s.de().map(|s| (guid, s))) + }) + .collect() +} diff --git a/core/startos/src/registry/signer.rs b/core/startos/src/registry/signer.rs new file mode 100644 index 000000000..bf5374d75 --- /dev/null +++ b/core/startos/src/registry/signer.rs @@ -0,0 +1,477 @@ +use std::collections::{HashMap, HashSet}; +use std::path::Path; +use std::str::FromStr; + +use clap::builder::ValueParserFactory; +use imbl_value::InternedString; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha512}; +use tokio::io::AsyncWrite; +use ts_rs::TS; +use url::Url; + +use crate::prelude::*; +use crate::s9pk::merkle_archive::source::http::HttpSource; +use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; +use crate::s9pk::merkle_archive::source::{ArchiveSource, FileSource}; +use crate::util::clap::FromStrParser; +use crate::util::serde::{Base64, Pem}; + +#[derive(Debug, Deserialize, Serialize, HasModel, TS)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +#[ts(export)] +pub struct SignerInfo { + pub name: String, + pub contact: Vec, + pub keys: HashSet, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +#[serde(tag = "alg", content = "pubkey")] +pub enum SignerKey { + Ed25519(Pem), +} +impl SignerKey { + pub fn verifier(&self) -> Verifier { + match self { + Self::Ed25519(k) => Verifier::Ed25519(*k, Sha512::new()), + } + } + pub fn verify_message( + &self, + message: &[u8], + signature: &[u8], + context: &str, + ) -> Result<(), Error> { + let mut v = self.verifier(); + v.update(message); + v.verify(signature, context) + } +} +impl std::fmt::Display for SignerKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Ed25519(k) => write!(f, "{k}"), + } + } +} + +pub enum Verifier { + Ed25519(Pem, Sha512), +} +impl Verifier { + pub fn update(&mut self, data: &[u8]) { + match self { + Self::Ed25519(_, h) => h.update(data), + } + } + pub fn verify(self, signature: &[u8], context: &str) -> Result<(), Error> { + match self { + Self::Ed25519(k, h) => k.verify_prehashed_strict( + h, + Some(context.as_bytes()), + &ed25519_dalek::Signature::from_slice(signature)?, + )?, + } + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +// TODO: better types +pub enum ContactInfo { + Email(String), + Matrix(String), + Website(#[ts(type = "string")] Url), +} +impl std::fmt::Display for ContactInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Email(e) => write!(f, "mailto:{e}"), + Self::Matrix(m) => write!(f, "https://matrix.to/#/{m}"), + Self::Website(w) => write!(f, "{w}"), + } + } +} +impl FromStr for ContactInfo { + type Err = Error; + fn from_str(s: &str) -> Result { + Ok(if let Some(s) = s.strip_prefix("mailto:") { + Self::Email(s.to_owned()) + } else if let Some(s) = s.strip_prefix("https://matrix.to/#/") { + Self::Matrix(s.to_owned()) + } else { + Self::Website(s.parse()?) + }) + } +} +impl ValueParserFactory for ContactInfo { + type Parser = FromStrParser; + fn value_parser() -> Self::Parser { + Self::Parser::new() + } +} + +#[derive(Debug, Deserialize, Serialize, HasModel, TS)] +#[serde(rename_all = "kebab-case")] +#[model = "Model"] +#[ts(export)] +pub struct SignatureInfo { + #[ts(type = "string")] + pub context: InternedString, + pub blake3_ed255i9: Option, +} +impl SignatureInfo { + pub fn new(context: &str) -> Self { + Self { + context: context.into(), + blake3_ed255i9: None, + } + } + pub fn validate(&self, accept: AcceptSigners) -> Result { + FileValidator::from_signatures(self.signatures(), accept, &self.context) + } + pub fn all_signers(&self) -> AcceptSigners { + AcceptSigners::All( + self.signatures() + .map(|s| AcceptSigners::Signer(s.signer())) + .collect(), + ) + .flatten() + } + pub fn signatures(&self) -> impl Iterator + '_ { + self.blake3_ed255i9.iter().flat_map(|info| { + info.signatures + .iter() + .map(|(k, s)| (k.clone(), *s)) + .map(|(pubkey, signature)| { + Signature::Blake3Ed25519(Blake3Ed25519Signature { + hash: info.hash, + size: info.size, + pubkey, + signature, + }) + }) + }) + } + pub fn add_sig(&mut self, signature: &Signature) -> Result<(), Error> { + signature.validate(&self.context)?; + match signature { + Signature::Blake3Ed25519(s) => { + if self + .blake3_ed255i9 + .as_ref() + .map_or(true, |info| info.hash == s.hash) + { + let new = if let Some(mut info) = self.blake3_ed255i9.take() { + info.signatures.insert(s.pubkey, s.signature); + info + } else { + s.info() + }; + self.blake3_ed255i9 = Some(new); + Ok(()) + } else { + Err(Error::new( + eyre!("hash sum mismatch"), + ErrorKind::InvalidSignature, + )) + } + } + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub enum AcceptSigners { + #[serde(skip)] + Accepted(Signature), + Signer(SignerKey), + Any(Vec), + All(Vec), +} +impl AcceptSigners { + const fn null() -> Self { + Self::Any(Vec::new()) + } + pub fn flatten(self) -> Self { + match self { + Self::Any(mut s) | Self::All(mut s) if s.len() == 1 => s.swap_remove(0).flatten(), + s => s, + } + } + pub fn accepted(&self) -> bool { + match self { + Self::Accepted(_) => true, + Self::All(s) => s.iter().all(|s| s.accepted()), + _ => false, + } + } + pub fn try_accept( + self, + context: &str, + ) -> Box> + Send + Sync + '_> { + match self { + Self::Accepted(s) => Box::new(std::iter::once(s).map(|s| { + s.validate(context)?; + Ok(s) + })), + Self::All(s) => Box::new(s.into_iter().flat_map(|s| s.try_accept(context))), + _ => Box::new(std::iter::once(Err(Error::new( + eyre!("signer(s) not accepted"), + ErrorKind::InvalidSignature, + )))), + } + } + pub fn process_signature(&mut self, sig: &Signature) { + let new = match std::mem::replace(self, Self::null()) { + Self::Accepted(s) => Self::Accepted(s), + Self::Signer(s) => { + if s == sig.signer() { + Self::Accepted(sig.clone()) + } else { + Self::Signer(s) + } + } + Self::All(mut s) => { + s.iter_mut().for_each(|s| s.process_signature(sig)); + + Self::All(s) + } + Self::Any(mut s) => { + if let Some(s) = s + .iter_mut() + .map(|s| { + s.process_signature(sig); + s + }) + .filter(|s| s.accepted()) + .next() + { + std::mem::replace(s, Self::null()) + } else { + Self::Any(s) + } + } + }; + *self = new; + } +} + +#[must_use] +pub struct FileValidator { + blake3: Option, + size: Option, +} +impl FileValidator { + fn add_blake3(&mut self, hash: [u8; 32], size: u64) -> Result<(), Error> { + if let Some(h) = self.blake3 { + ensure_code!(h == hash, ErrorKind::InvalidSignature, "hash sum mismatch"); + } + self.blake3 = Some(blake3::Hash::from_bytes(hash)); + if let Some(s) = self.size { + ensure_code!(s == size, ErrorKind::InvalidSignature, "file size mismatch"); + } + self.size = Some(size); + Ok(()) + } + pub fn blake3(&self) -> Result { + if let Some(hash) = self.blake3 { + Ok(hash) + } else { + Err(Error::new( + eyre!("no BLAKE3 signatures found"), + ErrorKind::InvalidSignature, + )) + } + } + pub fn size(&self) -> Result { + if let Some(size) = self.size { + Ok(size) + } else { + Err(Error::new( + eyre!("no signatures found"), + ErrorKind::InvalidSignature, + )) + } + } + pub fn from_signatures( + signatures: impl IntoIterator, + mut accept: AcceptSigners, + context: &str, + ) -> Result { + let mut res = Self { + blake3: None, + size: None, + }; + for signature in signatures { + accept.process_signature(&signature); + } + for signature in accept.try_accept(context) { + match signature? { + Signature::Blake3Ed25519(s) => res.add_blake3(*s.hash, s.size)?, + } + } + + Ok(res) + } + pub async fn download( + &self, + url: Url, + client: Client, + dst: &mut (impl AsyncWrite + Unpin + Send + ?Sized), + ) -> Result<(), Error> { + let src = HttpSource::new(client, url).await?; + let (Some(hash), Some(size)) = (self.blake3, self.size) else { + return Err(Error::new( + eyre!("no BLAKE3 signatures found"), + ErrorKind::InvalidSignature, + )); + }; + src.section(0, size) + .copy_verify(dst, Some((hash, size))) + .await?; + + Ok(()) + } + pub async fn validate_file(&self, file: &MultiCursorFile) -> Result<(), Error> { + ensure_code!( + file.size().await == Some(self.size()?), + ErrorKind::InvalidSignature, + "file size mismatch" + ); + ensure_code!( + file.blake3_mmap().await? == self.blake3()?, + ErrorKind::InvalidSignature, + "hash sum mismatch" + ); + Ok(()) + } +} + +#[derive(Debug, Deserialize, Serialize, HasModel, TS)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +#[ts(export)] +pub struct Blake3Ed2551SignatureInfo { + pub hash: Base64<[u8; 32]>, + pub size: u64, + pub signatures: HashMap, Base64<[u8; 64]>>, +} +impl Blake3Ed2551SignatureInfo { + pub fn validate(&self, context: &str) -> Result>, Error> { + self.signatures + .iter() + .map(|(k, s)| { + let sig = Blake3Ed25519Signature { + hash: self.hash, + size: self.size, + pubkey: k.clone(), + signature: *s, + }; + sig.validate(context)?; + Ok(sig.pubkey) + }) + .collect() + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub enum Signature { + Blake3Ed25519(Blake3Ed25519Signature), +} +impl Signature { + pub fn validate(&self, context: &str) -> Result<(), Error> { + match self { + Self::Blake3Ed25519(a) => a.validate(context), + } + } + pub fn signer(&self) -> SignerKey { + match self { + Self::Blake3Ed25519(s) => SignerKey::Ed25519(s.pubkey.clone()), + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct Blake3Ed25519Signature { + pub hash: Base64<[u8; 32]>, + pub size: u64, + pub pubkey: Pem, + // ed25519-sig(sha512(blake3(file) + len_u64_be(file))) + pub signature: Base64<[u8; 64]>, +} +impl Blake3Ed25519Signature { + pub async fn sign_file( + key: &ed25519_dalek::SigningKey, + file: &MultiCursorFile, + context: &str, + ) -> Result { + let size = file + .size() + .await + .ok_or_else(|| Error::new(eyre!("failed to get file size"), ErrorKind::Filesystem))?; + let hash = file.blake3_mmap().await?; + let signature = key.sign_prehashed( + Sha512::new_with_prefix(hash.as_bytes()).chain_update(u64::to_be_bytes(size)), + Some(context.as_bytes()), + )?; + Ok(Self { + hash: Base64(*hash.as_bytes()), + size, + pubkey: Pem::new(key.verifying_key()), + signature: Base64(signature.to_bytes()), + }) + } + + pub fn validate(&self, context: &str) -> Result<(), Error> { + let sig = ed25519_dalek::Signature::from_bytes(&*self.signature); + self.pubkey.verify_prehashed_strict( + Sha512::new_with_prefix(*self.hash).chain_update(u64::to_be_bytes(self.size)), + Some(context.as_bytes()), + &sig, + )?; + Ok(()) + } + + pub async fn check_file(&self, file: &MultiCursorFile) -> Result<(), Error> { + let size = file + .size() + .await + .ok_or_else(|| Error::new(eyre!("failed to get file size"), ErrorKind::Filesystem))?; + if self.size != size { + return Err(Error::new( + eyre!("incorrect file size: expected {} got {}", self.size, size), + ErrorKind::InvalidSignature, + )); + } + let hash = file.blake3_mmap().await?; + if &*self.hash != hash.as_bytes() { + return Err(Error::new( + eyre!("hash sum mismatch"), + ErrorKind::InvalidSignature, + )); + } + Ok(()) + } + + pub fn info(&self) -> Blake3Ed2551SignatureInfo { + Blake3Ed2551SignatureInfo { + hash: self.hash, + size: self.size, + signatures: [(self.pubkey, self.signature)].into_iter().collect(), + } + } +} diff --git a/core/startos/src/rpc_continuations.rs b/core/startos/src/rpc_continuations.rs new file mode 100644 index 000000000..04b88ea8e --- /dev/null +++ b/core/startos/src/rpc_continuations.rs @@ -0,0 +1,142 @@ +use std::collections::BTreeMap; +use std::str::FromStr; +use std::time::Duration; + +use axum::extract::ws::WebSocket; +use axum::extract::Request; +use axum::response::Response; +use clap::builder::ValueParserFactory; +use futures::future::BoxFuture; +use helpers::TimedResource; +use imbl_value::InternedString; +use tokio::sync::Mutex; + +#[allow(unused_imports)] +use crate::prelude::*; +use crate::util::clap::FromStrParser; +use crate::util::new_guid; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] +pub struct RequestGuid(InternedString); +impl RequestGuid { + pub fn new() -> Self { + Self(new_guid()) + } + + pub fn from(r: &str) -> Option { + if r.len() != 32 { + return None; + } + for c in r.chars() { + if !(c >= 'A' && c <= 'Z' || c >= '2' && c <= '7') { + return None; + } + } + Some(RequestGuid(InternedString::intern(r))) + } +} +impl AsRef for RequestGuid { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} +impl FromStr for RequestGuid { + type Err = Error; + fn from_str(s: &str) -> Result { + Self::from(s).ok_or_else(|| Error::new(eyre!("invalid guid"), ErrorKind::Deserialization)) + } +} +impl ValueParserFactory for RequestGuid { + type Parser = FromStrParser; + fn value_parser() -> Self::Parser { + Self::Parser::new() + } +} + +#[test] +fn parse_guid() { + println!( + "{:?}", + RequestGuid::from(&format!("{}", RequestGuid::new())) + ) +} + +impl std::fmt::Display for RequestGuid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +pub type RestHandler = + Box BoxFuture<'static, Result> + Send>; + +pub type WebSocketHandler = Box BoxFuture<'static, ()> + Send>; + +pub enum RpcContinuation { + Rest(TimedResource), + WebSocket(TimedResource), +} +impl RpcContinuation { + pub fn rest(handler: RestHandler, timeout: Duration) -> Self { + RpcContinuation::Rest(TimedResource::new(handler, timeout)) + } + pub fn ws(handler: WebSocketHandler, timeout: Duration) -> Self { + RpcContinuation::WebSocket(TimedResource::new(handler, timeout)) + } + pub fn is_timed_out(&self) -> bool { + match self { + RpcContinuation::Rest(a) => a.is_timed_out(), + RpcContinuation::WebSocket(a) => a.is_timed_out(), + } + } +} + +pub struct RpcContinuations(Mutex>); +impl RpcContinuations { + pub fn new() -> Self { + RpcContinuations(Mutex::new(BTreeMap::new())) + } + + #[instrument(skip_all)] + pub async fn clean(&self) { + let mut continuations = self.0.lock().await; + let mut to_remove = Vec::new(); + for (guid, cont) in &*continuations { + if cont.is_timed_out() { + to_remove.push(guid.clone()); + } + } + for guid in to_remove { + continuations.remove(&guid); + } + } + + #[instrument(skip_all)] + pub async fn add(&self, guid: RequestGuid, handler: RpcContinuation) { + self.clean().await; + self.0.lock().await.insert(guid, handler); + } + + pub async fn get_ws_handler(&self, guid: &RequestGuid) -> Option { + let mut continuations = self.0.lock().await; + if !matches!(continuations.get(guid), Some(RpcContinuation::WebSocket(_))) { + return None; + } + let Some(RpcContinuation::WebSocket(x)) = continuations.remove(guid) else { + return None; + }; + x.get().await + } + + pub async fn get_rest_handler(&self, guid: &RequestGuid) -> Option { + let mut continuations: tokio::sync::MutexGuard<'_, BTreeMap> = + self.0.lock().await; + if !matches!(continuations.get(guid), Some(RpcContinuation::Rest(_))) { + return None; + } + let Some(RpcContinuation::Rest(x)) = continuations.remove(guid) else { + return None; + }; + x.get().await + } +} diff --git a/core/startos/src/s9pk/merkle_archive/directory_contents.rs b/core/startos/src/s9pk/merkle_archive/directory_contents.rs index c5373a31b..19821cf32 100644 --- a/core/startos/src/s9pk/merkle_archive/directory_contents.rs +++ b/core/startos/src/s9pk/merkle_archive/directory_contents.rs @@ -3,6 +3,7 @@ use std::fmt::Debug; use std::path::{Path, PathBuf}; use std::sync::Arc; +use blake3::Hash; use futures::future::BoxFuture; use futures::FutureExt; use imbl::OrdMap; @@ -11,11 +12,11 @@ use itertools::Itertools; use tokio::io::AsyncRead; use crate::prelude::*; -use crate::s9pk::merkle_archive::hash::{Hash, HashWriter}; use crate::s9pk::merkle_archive::sink::{Sink, TrackingWriter}; use crate::s9pk::merkle_archive::source::{ArchiveSource, DynFileSource, FileSource, Section}; use crate::s9pk::merkle_archive::write_queue::WriteQueue; use crate::s9pk::merkle_archive::{varint, Entry, EntryContents}; +use crate::util::io::ParallelBlake3Writer; #[derive(Clone)] pub struct DirectoryContents { @@ -155,7 +156,7 @@ impl DirectoryContents> { pub fn deserialize<'a>( source: &'a S, header: &'a mut (impl AsyncRead + Unpin + Send), - sighash: Hash, + (sighash, max_size): (Hash, u64), ) -> BoxFuture<'a, Result> { async move { use tokio::io::AsyncReadExt; @@ -168,15 +169,20 @@ impl DirectoryContents> { header.read_exact(&mut size).await?; let size = u64::from_be_bytes(size); + ensure_code!( + size <= max_size, + ErrorKind::InvalidSignature, + "size is greater than signed" + ); + let mut toc_reader = source.fetch(position, size).await?; let len = varint::deserialize_varint(&mut toc_reader).await?; let mut entries = OrdMap::new(); for _ in 0..len { - entries.insert( - varint::deserialize_varstring(&mut toc_reader).await?.into(), - Entry::deserialize(source, &mut toc_reader).await?, - ); + let name = varint::deserialize_varstring(&mut toc_reader).await?; + let entry = Entry::deserialize(source, &mut toc_reader).await?; + entries.insert(name.into(), entry); } let res = Self { @@ -233,7 +239,8 @@ impl DirectoryContents { #[instrument(skip_all)] pub fn sighash<'a>(&'a self) -> BoxFuture<'a, Result> { async move { - let mut hasher = TrackingWriter::new(0, HashWriter::new()); + let mut hasher = + TrackingWriter::new(0, ParallelBlake3Writer::new(super::hash::BUFFER_CAPACITY)); let mut sig_contents = OrdMap::new(); for (name, entry) in &**self { sig_contents.insert(name.clone(), entry.to_missing().await?); @@ -244,7 +251,8 @@ impl DirectoryContents { } .serialize_toc(&mut WriteQueue::new(0), &mut hasher) .await?; - Ok(hasher.into_inner().finalize()) + let hash = hasher.into_inner().finalize().await?; + Ok(hash) } .boxed() } @@ -267,7 +275,9 @@ impl DirectoryContents { _ => std::cmp::Ordering::Equal, }) { varint::serialize_varstring(&**name, w).await?; - entry.serialize_header(queue.add(entry).await?, w).await?; + if let Some(pos) = entry.serialize_header(queue.add(entry).await?, w).await? { + eprintln!("DEBUG ====> {name} @ {pos}"); + } } Ok(()) diff --git a/core/startos/src/s9pk/merkle_archive/file_contents.rs b/core/startos/src/s9pk/merkle_archive/file_contents.rs index 7529fd2d0..a2d19ed88 100644 --- a/core/startos/src/s9pk/merkle_archive/file_contents.rs +++ b/core/startos/src/s9pk/merkle_archive/file_contents.rs @@ -1,9 +1,10 @@ +use blake3::Hash; use tokio::io::AsyncRead; use crate::prelude::*; -use crate::s9pk::merkle_archive::hash::{Hash, HashWriter}; use crate::s9pk::merkle_archive::sink::{Sink, TrackingWriter}; use crate::s9pk::merkle_archive::source::{ArchiveSource, DynFileSource, FileSource, Section}; +use crate::util::io::ParallelBlake3Writer; #[derive(Debug, Clone)] pub struct FileContents(S); @@ -13,7 +14,6 @@ impl FileContents { } pub const fn header_size() -> u64 { 8 // position: u64 BE - + 8 // size: u64 BE } } impl FileContents> { @@ -21,6 +21,7 @@ impl FileContents> { pub async fn deserialize( source: &S, header: &mut (impl AsyncRead + Unpin + Send), + size: u64, ) -> Result { use tokio::io::AsyncReadExt; @@ -28,27 +29,23 @@ impl FileContents> { header.read_exact(&mut position).await?; let position = u64::from_be_bytes(position); - let mut size = [0u8; 8]; - header.read_exact(&mut size).await?; - let size = u64::from_be_bytes(size); - Ok(Self(source.section(position, size))) } } impl FileContents { - pub async fn hash(&self) -> Result { - let mut hasher = TrackingWriter::new(0, HashWriter::new()); + pub async fn hash(&self) -> Result<(Hash, u64), Error> { + let mut hasher = + TrackingWriter::new(0, ParallelBlake3Writer::new(super::hash::BUFFER_CAPACITY)); self.serialize_body(&mut hasher, None).await?; - Ok(hasher.into_inner().finalize()) + let size = hasher.position(); + let hash = hasher.into_inner().finalize().await?; + Ok((hash, size)) } #[instrument(skip_all)] pub async fn serialize_header(&self, position: u64, w: &mut W) -> Result { use tokio::io::AsyncWriteExt; - let size = self.0.size().await?; - w.write_all(&position.to_be_bytes()).await?; - w.write_all(&size.to_be_bytes()).await?; Ok(position) } @@ -56,21 +53,9 @@ impl FileContents { pub async fn serialize_body( &self, w: &mut W, - verify: Option, + verify: Option<(Hash, u64)>, ) -> Result<(), Error> { - let start = if verify.is_some() { - Some(w.current_position().await?) - } else { - None - }; self.0.copy_verify(w, verify).await?; - if let Some(start) = start { - ensure_code!( - w.current_position().await? - start == self.0.size().await?, - ErrorKind::Pack, - "FileSource::copy wrote a number of bytes that does not match FileSource::size" - ); - } Ok(()) } pub fn into_dyn(self) -> FileContents { diff --git a/core/startos/src/s9pk/merkle_archive/hash.rs b/core/startos/src/s9pk/merkle_archive/hash.rs index ae2829012..fc635c435 100644 --- a/core/startos/src/s9pk/merkle_archive/hash.rs +++ b/core/startos/src/s9pk/merkle_archive/hash.rs @@ -1,68 +1,57 @@ -pub use blake3::Hash; -use blake3::Hasher; +use std::task::Poll; + +use blake3::Hash; use tokio::io::AsyncWrite; +use tokio_util::either::Either; use crate::prelude::*; +use crate::util::io::{ParallelBlake3Writer, TeeWriter}; -#[pin_project::pin_project] -pub struct HashWriter { - hasher: Hasher, -} -impl HashWriter { - pub fn new() -> Self { - Self { - hasher: Hasher::new(), - } - } - pub fn finalize(self) -> Hash { - self.hasher.finalize() - } -} -impl AsyncWrite for HashWriter { - fn poll_write( - self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - buf: &[u8], - ) -> std::task::Poll> { - self.project().hasher.update(buf); - std::task::Poll::Ready(Ok(buf.len())) - } - fn poll_flush( - self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::task::Poll::Ready(Ok(())) - } - fn poll_shutdown( - self: std::pin::Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::task::Poll::Ready(Ok(())) - } -} +pub const BUFFER_CAPACITY: usize = 10 * 1024 * 1024; // 10MiB #[pin_project::pin_project] pub struct VerifyingWriter { - verify: Option<(Hasher, Hash)>, + verify: Option<(Hash, u64)>, #[pin] - writer: W, + writer: Either, W>, } impl VerifyingWriter { - pub fn new(w: W, verify: Option) -> Self { + pub fn new(w: W, verify: Option<(Hash, u64)>) -> Self { Self { - verify: verify.map(|v| (Hasher::new(), v)), - writer: w, + writer: if verify.is_some() { + Either::Left(TeeWriter::new( + w, + ParallelBlake3Writer::new(BUFFER_CAPACITY), + BUFFER_CAPACITY, + )) + } else { + Either::Right(w) + }, + verify, } } - pub fn verify(self) -> Result { - if let Some((actual, expected)) = self.verify { - ensure_code!( - actual.finalize() == expected, - ErrorKind::InvalidSignature, - "hash sum does not match" - ); +} +impl VerifyingWriter { + pub async fn verify(self) -> Result { + match self.writer { + Either::Left(writer) => { + let (writer, actual) = writer.into_inner().await?; + if let Some((expected, remaining)) = self.verify { + ensure_code!( + actual.finalize().await? == expected, + ErrorKind::InvalidSignature, + "hash sum mismatch" + ); + ensure_code!( + remaining == 0, + ErrorKind::InvalidSignature, + "file size mismatch" + ); + } + Ok(writer) + } + Either::Right(writer) => Ok(writer), } - Ok(self.writer) } } impl AsyncWrite for VerifyingWriter { @@ -70,28 +59,35 @@ impl AsyncWrite for VerifyingWriter { self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, buf: &[u8], - ) -> std::task::Poll> { + ) -> Poll> { let this = self.project(); - match this.writer.poll_write(cx, buf) { - std::task::Poll::Ready(Ok(written)) => { - if let Some((h, _)) = this.verify { - h.update(&buf[..written]); - } - std::task::Poll::Ready(Ok(written)) + if let Some((_, remaining)) = this.verify { + if *remaining < buf.len() as u64 { + return Poll::Ready(Err(std::io::Error::other(eyre!( + "attempted to write more bytes than signed" + )))); + } + } + match this.writer.poll_write(cx, buf)? { + Poll::Pending => Poll::Pending, + Poll::Ready(n) => { + if let Some((_, remaining)) = this.verify { + *remaining -= n as u64; + } + Poll::Ready(Ok(n)) } - a => a, } } fn poll_flush( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { + ) -> Poll> { self.project().writer.poll_flush(cx) } fn poll_shutdown( self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { + ) -> Poll> { self.project().writer.poll_shutdown(cx) } } diff --git a/core/startos/src/s9pk/merkle_archive/mod.rs b/core/startos/src/s9pk/merkle_archive/mod.rs index afd00032a..3863ceeaf 100644 --- a/core/startos/src/s9pk/merkle_archive/mod.rs +++ b/core/startos/src/s9pk/merkle_archive/mod.rs @@ -1,12 +1,14 @@ use std::path::Path; +use blake3::Hash; use ed25519_dalek::{Signature, SigningKey, VerifyingKey}; +use imbl_value::InternedString; +use sha2::{Digest, Sha512}; use tokio::io::AsyncRead; use crate::prelude::*; use crate::s9pk::merkle_archive::directory_contents::DirectoryContents; use crate::s9pk::merkle_archive::file_contents::FileContents; -use crate::s9pk::merkle_archive::hash::Hash; use crate::s9pk::merkle_archive::sink::Sink; use crate::s9pk::merkle_archive::source::{ArchiveSource, DynFileSource, FileSource, Section}; use crate::s9pk::merkle_archive::write_queue::WriteQueue; @@ -23,8 +25,8 @@ pub mod write_queue; #[derive(Debug, Clone)] enum Signer { - Signed(VerifyingKey, Signature), - Signer(SigningKey), + Signed(VerifyingKey, Signature, u64, InternedString), + Signer(SigningKey, InternedString), } #[derive(Debug, Clone)] @@ -33,22 +35,23 @@ pub struct MerkleArchive { contents: DirectoryContents, } impl MerkleArchive { - pub fn new(contents: DirectoryContents, signer: SigningKey) -> Self { + pub fn new(contents: DirectoryContents, signer: SigningKey, context: &str) -> Self { Self { - signer: Signer::Signer(signer), + signer: Signer::Signer(signer, context.into()), contents, } } pub fn signer(&self) -> VerifyingKey { match &self.signer { - Signer::Signed(k, _) => *k, - Signer::Signer(k) => k.verifying_key(), + Signer::Signed(k, _, _, _) => *k, + Signer::Signer(k, _) => k.verifying_key(), } } pub const fn header_size() -> u64 { 32 // pubkey + 64 // signature + 32 // sighash + + 8 // size + DirectoryContents::>::header_size() } pub fn contents(&self) -> &DirectoryContents { @@ -57,8 +60,8 @@ impl MerkleArchive { pub fn contents_mut(&mut self) -> &mut DirectoryContents { &mut self.contents } - pub fn set_signer(&mut self, key: SigningKey) { - self.signer = Signer::Signer(key); + pub fn set_signer(&mut self, key: SigningKey, context: &str) { + self.signer = Signer::Signer(key, context.into()); } pub fn sort_by( &mut self, @@ -71,6 +74,7 @@ impl MerkleArchive> { #[instrument(skip_all)] pub async fn deserialize( source: &S, + context: &str, header: &mut (impl AsyncRead + Unpin + Send), ) -> Result { use tokio::io::AsyncReadExt; @@ -87,12 +91,20 @@ impl MerkleArchive> { header.read_exact(&mut sighash).await?; let sighash = Hash::from_bytes(sighash); - let contents = DirectoryContents::deserialize(source, header, sighash).await?; + let mut max_size = [0u8; 8]; + header.read_exact(&mut max_size).await?; + let max_size = u64::from_be_bytes(max_size); - pubkey.verify_strict(contents.sighash().await?.as_bytes(), &signature)?; + pubkey.verify_prehashed_strict( + Sha512::new_with_prefix(sighash.as_bytes()).chain_update(&u64::to_be_bytes(max_size)), + Some(context.as_bytes()), + &signature, + )?; + + let contents = DirectoryContents::deserialize(source, header, (sighash, max_size)).await?; Ok(Self { - signer: Signer::Signed(pubkey, signature), + signer: Signer::Signed(pubkey, signature, max_size, context.into()), contents, }) } @@ -109,15 +121,26 @@ impl MerkleArchive { use tokio::io::AsyncWriteExt; let sighash = self.contents.sighash().await?; + let size = self.contents.toc_size(); - let (pubkey, signature) = match &self.signer { - Signer::Signed(pubkey, signature) => (*pubkey, *signature), - Signer::Signer(s) => (s.into(), ed25519_dalek::Signer::sign(s, sighash.as_bytes())), + let (pubkey, signature, max_size) = match &self.signer { + Signer::Signed(pubkey, signature, max_size, _) => (*pubkey, *signature, *max_size), + Signer::Signer(s, context) => ( + s.into(), + ed25519_dalek::SigningKey::sign_prehashed( + s, + Sha512::new_with_prefix(sighash.as_bytes()) + .chain_update(&u64::to_be_bytes(size)), + Some(context.as_bytes()), + )?, + size, + ), }; w.write_all(pubkey.as_bytes()).await?; w.write_all(&signature.to_bytes()).await?; w.write_all(sighash.as_bytes()).await?; + w.write_all(&u64::to_be_bytes(max_size)).await?; let mut next_pos = w.current_position().await?; next_pos += DirectoryContents::::header_size(); self.contents.serialize_header(next_pos, w).await?; @@ -137,7 +160,7 @@ impl MerkleArchive { #[derive(Debug, Clone)] pub struct Entry { - hash: Option, + hash: Option<(Hash, u64)>, contents: EntryContents, } impl Entry { @@ -150,7 +173,7 @@ impl Entry { pub fn file(source: S) -> Self { Self::new(EntryContents::File(FileContents::new(source))) } - pub fn hash(&self) -> Option { + pub fn hash(&self) -> Option<(Hash, u64)> { self.hash } pub fn as_contents(&self) -> &EntryContents { @@ -189,6 +212,7 @@ impl Entry { } pub fn header_size(&self) -> u64 { 32 // hash + + 8 // size: u64 BE + self.contents.header_size() } } @@ -205,10 +229,14 @@ impl Entry> { header.read_exact(&mut hash).await?; let hash = Hash::from_bytes(hash); - let contents = EntryContents::deserialize(source, header, hash).await?; + let mut size = [0u8; 8]; + header.read_exact(&mut size).await?; + let size = u64::from_be_bytes(size); + + let contents = EntryContents::deserialize(source, header, (hash, size)).await?; Ok(Self { - hash: Some(hash), + hash: Some((hash, size)), contents, }) } @@ -258,12 +286,13 @@ impl Entry { ) -> Result, Error> { use tokio::io::AsyncWriteExt; - let hash = if let Some(hash) = self.hash { + let (hash, size) = if let Some(hash) = self.hash { hash } else { self.contents.hash().await? }; w.write_all(hash.as_bytes()).await?; + w.write_all(&u64::to_be_bytes(size)).await?; self.contents.serialize_header(position, w).await } pub fn into_dyn(self) -> Entry { @@ -305,7 +334,7 @@ impl EntryContents> { pub async fn deserialize( source: &S, header: &mut (impl AsyncRead + Unpin + Send), - hash: Hash, + (hash, size): (Hash, u64), ) -> Result { use tokio::io::AsyncReadExt; @@ -313,9 +342,11 @@ impl EntryContents> { header.read_exact(&mut type_id).await?; match type_id[0] { 0 => Ok(Self::Missing), - 1 => Ok(Self::File(FileContents::deserialize(source, header).await?)), + 1 => Ok(Self::File( + FileContents::deserialize(source, header, size).await?, + )), 2 => Ok(Self::Directory( - DirectoryContents::deserialize(source, header, hash).await?, + DirectoryContents::deserialize(source, header, (hash, size)).await?, )), id => Err(Error::new( eyre!("Unknown type id {id} found in MerkleArchive"), @@ -325,14 +356,14 @@ impl EntryContents> { } } impl EntryContents { - pub async fn hash(&self) -> Result { + pub async fn hash(&self) -> Result<(Hash, u64), Error> { match self { Self::Missing => Err(Error::new( eyre!("Cannot compute hash of missing file"), ErrorKind::Pack, )), Self::File(f) => f.hash().await, - Self::Directory(d) => d.sighash().await, + Self::Directory(d) => Ok((d.sighash().await?, d.toc_size())), } } #[instrument(skip_all)] diff --git a/core/startos/src/s9pk/merkle_archive/sink.rs b/core/startos/src/s9pk/merkle_archive/sink.rs index c71377808..ec1431b8e 100644 --- a/core/startos/src/s9pk/merkle_archive/sink.rs +++ b/core/startos/src/s9pk/merkle_archive/sink.rs @@ -36,6 +36,9 @@ impl TrackingWriter { writer: w, } } + pub fn position(&self) -> u64 { + self.position + } pub fn into_inner(self) -> W { self.writer } diff --git a/core/startos/src/s9pk/merkle_archive/source/http.rs b/core/startos/src/s9pk/merkle_archive/source/http.rs index 1cb9ba961..738c480ed 100644 --- a/core/startos/src/s9pk/merkle_archive/source/http.rs +++ b/core/startos/src/s9pk/merkle_archive/source/http.rs @@ -3,7 +3,7 @@ use futures::stream::BoxStream; use futures::{StreamExt, TryStreamExt}; use reqwest::header::{ACCEPT_RANGES, CONTENT_LENGTH, RANGE}; use reqwest::{Client, Url}; -use tokio::io::AsyncRead; +use tokio::io::{AsyncRead, AsyncReadExt, Take}; use tokio_util::io::StreamReader; use crate::prelude::*; @@ -50,9 +50,8 @@ impl HttpSource { }) } } -#[async_trait::async_trait] impl ArchiveSource for HttpSource { - type Reader = HttpReader; + type Reader = Take; async fn size(&self) -> Option { self.size } @@ -72,7 +71,8 @@ impl ArchiveSource for HttpSource { .boxed() } else { futures::stream::empty().boxed() - }))), + })) + .take(size)), _ => todo!(), } } diff --git a/core/startos/src/s9pk/merkle_archive/source/mod.rs b/core/startos/src/s9pk/merkle_archive/source/mod.rs index 97c94b480..6e3b1e584 100644 --- a/core/startos/src/s9pk/merkle_archive/source/mod.rs +++ b/core/startos/src/s9pk/merkle_archive/source/mod.rs @@ -2,6 +2,8 @@ use std::path::PathBuf; use std::sync::Arc; use blake3::Hash; +use futures::future::BoxFuture; +use futures::{Future, FutureExt}; use tokio::fs::File; use tokio::io::{AsyncRead, AsyncWrite}; @@ -11,29 +13,51 @@ use crate::s9pk::merkle_archive::hash::VerifyingWriter; pub mod http; pub mod multi_cursor_file; -#[async_trait::async_trait] pub trait FileSource: Clone + Send + Sync + Sized + 'static { type Reader: AsyncRead + Unpin + Send; - async fn size(&self) -> Result; - async fn reader(&self) -> Result; - async fn copy(&self, w: &mut W) -> Result<(), Error> { - tokio::io::copy(&mut self.reader().await?, w).await?; - Ok(()) - } - async fn copy_verify( + fn size(&self) -> impl Future> + Send; + fn reader(&self) -> impl Future> + Send; + fn copy( &self, w: &mut W, - verify: Option, - ) -> Result<(), Error> { - let mut w = VerifyingWriter::new(w, verify); - tokio::io::copy(&mut self.reader().await?, &mut w).await?; - w.verify()?; - Ok(()) + ) -> impl Future> + Send { + async move { + tokio::io::copy(&mut self.reader().await?, w).await?; + Ok(()) + } } - async fn to_vec(&self, verify: Option) -> Result, Error> { - let mut vec = Vec::with_capacity(self.size().await? as usize); - self.copy_verify(&mut vec, verify).await?; - Ok(vec) + fn copy_verify( + &self, + w: &mut W, + verify: Option<(Hash, u64)>, + ) -> impl Future> + Send { + async move { + let mut w = VerifyingWriter::new(w, verify); + tokio::io::copy(&mut self.reader().await?, &mut w).await?; + w.verify().await?; + Ok(()) + } + } + fn to_vec( + &self, + verify: Option<(Hash, u64)>, + ) -> impl Future, Error>> + Send { + fn to_vec( + src: &impl FileSource, + verify: Option<(Hash, u64)>, + ) -> BoxFuture, Error>> { + async move { + let mut vec = Vec::with_capacity(if let Some((_, size)) = &verify { + *size + } else { + src.size().await? + } as usize); + src.copy_verify(&mut vec, verify).await?; + Ok(vec) + } + .boxed() + } + to_vec(self, verify) } } @@ -44,7 +68,6 @@ impl DynFileSource { Self(Arc::new(source)) } } -#[async_trait::async_trait] impl FileSource for DynFileSource { type Reader = Box; async fn size(&self) -> Result { @@ -62,11 +85,11 @@ impl FileSource for DynFileSource { async fn copy_verify( &self, mut w: &mut W, - verify: Option, + verify: Option<(Hash, u64)>, ) -> Result<(), Error> { self.0.copy_verify(&mut w, verify).await } - async fn to_vec(&self, verify: Option) -> Result, Error> { + async fn to_vec(&self, verify: Option<(Hash, u64)>) -> Result, Error> { self.0.to_vec(verify).await } } @@ -79,9 +102,9 @@ trait DynableFileSource: Send + Sync + 'static { async fn copy_verify( &self, w: &mut (dyn AsyncWrite + Unpin + Send), - verify: Option, + verify: Option<(Hash, u64)>, ) -> Result<(), Error>; - async fn to_vec(&self, verify: Option) -> Result, Error>; + async fn to_vec(&self, verify: Option<(Hash, u64)>) -> Result, Error>; } #[async_trait::async_trait] impl DynableFileSource for T { @@ -97,16 +120,15 @@ impl DynableFileSource for T { async fn copy_verify( &self, w: &mut (dyn AsyncWrite + Unpin + Send), - verify: Option, + verify: Option<(Hash, u64)>, ) -> Result<(), Error> { FileSource::copy_verify(self, w, verify).await } - async fn to_vec(&self, verify: Option) -> Result, Error> { + async fn to_vec(&self, verify: Option<(Hash, u64)>) -> Result, Error> { FileSource::to_vec(self, verify).await } } -#[async_trait::async_trait] impl FileSource for PathBuf { type Reader = File; async fn size(&self) -> Result { @@ -117,7 +139,6 @@ impl FileSource for PathBuf { } } -#[async_trait::async_trait] impl FileSource for Arc<[u8]> { type Reader = std::io::Cursor; async fn size(&self) -> Result { @@ -134,21 +155,26 @@ impl FileSource for Arc<[u8]> { } } -#[async_trait::async_trait] pub trait ArchiveSource: Clone + Send + Sync + Sized + 'static { type Reader: AsyncRead + Unpin + Send; - async fn size(&self) -> Option { - None + fn size(&self) -> impl Future> + Send { + async { None } } - async fn fetch(&self, position: u64, size: u64) -> Result; - async fn copy_to( + fn fetch( + &self, + position: u64, + size: u64, + ) -> impl Future> + Send; + fn copy_to( &self, position: u64, size: u64, w: &mut W, - ) -> Result<(), Error> { - tokio::io::copy(&mut self.fetch(position, size).await?, w).await?; - Ok(()) + ) -> impl Future> + Send { + async move { + tokio::io::copy(&mut self.fetch(position, size).await?, w).await?; + Ok(()) + } } fn section(&self, position: u64, size: u64) -> Section { Section { @@ -159,7 +185,6 @@ pub trait ArchiveSource: Clone + Send + Sync + Sized + 'static { } } -#[async_trait::async_trait] impl ArchiveSource for Arc<[u8]> { type Reader = tokio::io::Take>; async fn fetch(&self, position: u64, size: u64) -> Result { @@ -177,7 +202,6 @@ pub struct Section { position: u64, size: u64, } -#[async_trait::async_trait] impl FileSource for Section { type Reader = S::Reader; async fn size(&self) -> Result { diff --git a/core/startos/src/s9pk/merkle_archive/source/multi_cursor_file.rs b/core/startos/src/s9pk/merkle_archive/source/multi_cursor_file.rs index 7add68e6f..00188559a 100644 --- a/core/startos/src/s9pk/merkle_archive/source/multi_cursor_file.rs +++ b/core/startos/src/s9pk/merkle_archive/source/multi_cursor_file.rs @@ -31,6 +31,16 @@ impl MultiCursorFile { file: Arc::new(Mutex::new(File::open(path_from_fd(fd)).await?)), }) } + pub async fn blake3_mmap(&self) -> Result { + let path = self.path(); + tokio::task::spawn_blocking(move || { + let mut hasher = blake3::Hasher::new(); + hasher.update_mmap_rayon(path)?; + Ok(hasher.finalize()) + }) + .await + .with_kind(ErrorKind::Unknown)? + } } impl From for MultiCursorFile { fn from(value: File) -> Self { @@ -67,7 +77,6 @@ impl AsyncRead for FileSectionReader { } } -#[async_trait::async_trait] impl ArchiveSource for MultiCursorFile { type Reader = FileSectionReader; async fn size(&self) -> Option { diff --git a/core/startos/src/s9pk/merkle_archive/test.rs b/core/startos/src/s9pk/merkle_archive/test.rs index 430ab4f31..e902aafd9 100644 --- a/core/startos/src/s9pk/merkle_archive/test.rs +++ b/core/startos/src/s9pk/merkle_archive/test.rs @@ -52,7 +52,7 @@ fn test(files: Vec<(PathBuf, String)>) -> Result<(), Error> { } } let key = SigningKey::generate(&mut rand::thread_rng()); - let mut a1 = MerkleArchive::new(root, key); + let mut a1 = MerkleArchive::new(root, key, "test"); tokio::runtime::Builder::new_current_thread() .enable_io() .build() @@ -63,7 +63,7 @@ fn test(files: Vec<(PathBuf, String)>) -> Result<(), Error> { a1.serialize(&mut TrackingWriter::new(0, &mut s1), true) .await?; let s1: Arc<[u8]> = s1.into(); - let a2 = MerkleArchive::deserialize(&s1, &mut Cursor::new(s1.clone())).await?; + let a2 = MerkleArchive::deserialize(&s1, "test", &mut Cursor::new(s1.clone())).await?; for (path, content) in check_set { match a2 diff --git a/core/startos/src/s9pk/rpc.rs b/core/startos/src/s9pk/rpc.rs index 312d663fd..ca149c3e3 100644 --- a/core/startos/src/s9pk/rpc.rs +++ b/core/startos/src/s9pk/rpc.rs @@ -17,6 +17,7 @@ use crate::s9pk::manifest::Manifest; use crate::s9pk::merkle_archive::source::DynFileSource; use crate::s9pk::merkle_archive::Entry; use crate::s9pk::v2::compat::CONTAINER_TOOL; +use crate::s9pk::v2::SIG_CONTEXT; use crate::s9pk::S9pk; use crate::util::io::TmpDir; use crate::util::serde::{apply_expr, HandlerExtSerde}; @@ -24,7 +25,7 @@ use crate::util::Invoke; pub const SKIP_ENV: &[&str] = &["TERM", "container", "HOME", "HOSTNAME"]; -pub fn s9pk() -> ParentHandler { +pub fn s9pk() -> ParentHandler { ParentHandler::new() .subcommand("edit", edit()) .subcommand("inspect", inspect()) @@ -35,9 +36,9 @@ struct S9pkPath { s9pk: PathBuf, } -fn edit() -> ParentHandler { +fn edit() -> ParentHandler { let only_parent = |a, _| a; - ParentHandler::::new() + ParentHandler::new() .subcommand( "add-image", from_fn_async(add_image) @@ -52,9 +53,9 @@ fn edit() -> ParentHandler { ) } -fn inspect() -> ParentHandler { +fn inspect() -> ParentHandler { let only_parent = |a, _| a; - ParentHandler::::new() + ParentHandler::new() .subcommand( "file-tree", from_fn_async(file_tree) @@ -158,7 +159,7 @@ async fn add_image( .invoke(ErrorKind::Docker) .await?; let archive = s9pk.as_archive_mut(); - archive.set_signer(ctx.developer_key()?.clone()); + archive.set_signer(ctx.developer_key()?.clone(), SIG_CONTEXT); archive.contents_mut().insert_path( Path::new("images") .join(&arch) @@ -213,7 +214,7 @@ async fn edit_manifest( let tmp_path = s9pk_path.with_extension("s9pk.tmp"); let mut tmp_file = File::create(&tmp_path).await?; s9pk.as_archive_mut() - .set_signer(ctx.developer_key()?.clone()); + .set_signer(ctx.developer_key()?.clone(), SIG_CONTEXT); s9pk.serialize(&mut tmp_file, true).await?; tmp_file.sync_all().await?; tokio::fs::rename(&tmp_path, &s9pk_path).await?; diff --git a/core/startos/src/s9pk/v1/docker.rs b/core/startos/src/s9pk/v1/docker.rs index bbb1f2f09..96c532479 100644 --- a/core/startos/src/s9pk/v1/docker.rs +++ b/core/startos/src/s9pk/v1/docker.rs @@ -55,7 +55,6 @@ impl DockerReader { if let Some(image) = tokio_tar::Archive::new(rdr) .entries()? .try_filter_map(|e| { - let arch = arch.clone(); async move { Ok(if &*e.path()? == Path::new(&format!("{}.tar", arch)) { Some(e) diff --git a/core/startos/src/s9pk/v2/compat.rs b/core/startos/src/s9pk/v2/compat.rs index f95a7e4bf..109ea43b4 100644 --- a/core/startos/src/s9pk/v2/compat.rs +++ b/core/startos/src/s9pk/v2/compat.rs @@ -18,7 +18,7 @@ use crate::s9pk::merkle_archive::{Entry, MerkleArchive}; use crate::s9pk::rpc::SKIP_ENV; use crate::s9pk::v1::manifest::Manifest as ManifestV1; use crate::s9pk::v1::reader::S9pkReader; -use crate::s9pk::v2::S9pk; +use crate::s9pk::v2::{S9pk, SIG_CONTEXT}; use crate::util::io::TmpDir; use crate::util::Invoke; @@ -40,7 +40,6 @@ enum CompatSource { Buffered(Arc<[u8]>), File(PathBuf), } -#[async_trait::async_trait] impl FileSource for CompatSource { type Reader = Box; async fn size(&self) -> Result { @@ -315,7 +314,7 @@ impl S9pk> { )), )?; - let mut s9pk = S9pk::new(MerkleArchive::new(archive, signer), None).await?; + let mut s9pk = S9pk::new(MerkleArchive::new(archive, signer, SIG_CONTEXT), None).await?; let mut dest_file = File::create(destination.as_ref()).await?; s9pk.serialize(&mut dest_file, false).await?; dest_file.sync_all().await?; diff --git a/core/startos/src/s9pk/v2/mod.rs b/core/startos/src/s9pk/v2/mod.rs index 1051aaaf8..9452b7a72 100644 --- a/core/startos/src/s9pk/v2/mod.rs +++ b/core/startos/src/s9pk/v2/mod.rs @@ -17,6 +17,8 @@ use crate::ARCH; const MAGIC_AND_VERSION: &[u8] = &[0x3b, 0x3b, 0x02]; +pub const SIG_CONTEXT: &str = "s9pk"; + pub mod compat; pub mod manifest; @@ -191,7 +193,7 @@ impl S9pk> { "Invalid Magic or Unexpected Version" ); - let mut archive = MerkleArchive::deserialize(source, &mut header).await?; + let mut archive = MerkleArchive::deserialize(source, SIG_CONTEXT, &mut header).await?; if apply_filter { archive.filter(filter)?; diff --git a/core/startos/src/service/cli.rs b/core/startos/src/service/cli.rs index d3bdccd72..82c63d6a7 100644 --- a/core/startos/src/service/cli.rs +++ b/core/startos/src/service/cli.rs @@ -5,10 +5,11 @@ use clap::Parser; use imbl_value::Value; use once_cell::sync::OnceCell; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{call_remote_socket, yajrc, CallRemote, Context}; +use rpc_toolkit::{call_remote_socket, yajrc, CallRemote, Context, Empty}; use tokio::runtime::Runtime; use crate::lxc::HOST_RPC_SERVER_SOCKET; +use crate::service::service_effect_handler::EffectContext; #[derive(Debug, Default, Parser)] pub struct ContainerClientConfig { @@ -48,9 +49,8 @@ impl Context for ContainerCliContext { } } -#[async_trait::async_trait] -impl CallRemote for ContainerCliContext { - async fn call_remote(&self, method: &str, params: Value) -> Result { +impl CallRemote for ContainerCliContext { + async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { call_remote_socket( tokio::net::UnixStream::connect(&self.0.socket) .await diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index a4e3a4858..a9ee0bd81 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -7,14 +7,13 @@ use futures::future::BoxFuture; use imbl::OrdMap; use models::{HealthCheckId, PackageId, ProcedureName}; use persistent_container::PersistentContainer; -use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, Handler, HandlerArgs}; +use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, HandlerArgs, HandlerFor}; use serde::{Deserialize, Serialize}; use start_stop::StartStop; use tokio::sync::Notify; use ts_rs::TS; use crate::context::{CliContext, RpcContext}; -use crate::core::rpc_continuations::RequestGuid; use crate::db::model::package::{ InstalledState, PackageDataEntry, PackageState, PackageStateMatchModelRef, UpdatingState, }; @@ -23,6 +22,7 @@ use crate::install::PKG_ARCHIVE_DIR; use crate::lxc::ContainerId; use crate::prelude::*; use crate::progress::{NamedProgress, Progress}; +use crate::rpc_continuations::RequestGuid; use crate::s9pk::S9pk; use crate::service::service_map::InstallProgressHandles; use crate::service::transition::TransitionKind; @@ -510,11 +510,25 @@ pub async fn connect_rpc( } pub async fn connect_rpc_cli( - handle_args: HandlerArgs, + HandlerArgs { + context, + parent_method, + method, + params, + inherited_params, + raw_params, + }: HandlerArgs, ) -> Result<(), Error> { - let ctx = handle_args.context.clone(); - let guid = CallRemoteHandler::::new(from_fn_async(connect_rpc)) - .handle_async(handle_args) + let ctx = context.clone(); + let guid = CallRemoteHandler::::new(from_fn_async(connect_rpc)) + .handle_async(HandlerArgs { + context, + parent_method, + method, + params: rpc_toolkit::util::Flat(params, Empty {}), + inherited_params, + raw_params, + }) .await?; crate::lxc::connect_cli(&ctx, guid).await diff --git a/core/startos/src/service/persistent_container.rs b/core/startos/src/service/persistent_container.rs index e69498e5f..2cb68a121 100644 --- a/core/startos/src/service/persistent_container.rs +++ b/core/startos/src/service/persistent_container.rs @@ -10,7 +10,7 @@ use imbl_value::InternedString; use models::{ProcedureName, VolumeId}; use rpc_toolkit::{Empty, Server, ShutdownHandle}; use serde::de::DeserializeOwned; -use tokio::fs::{create_dir_all, File}; +use tokio::fs::{ File}; use tokio::process::Command; use tokio::sync::{oneshot, watch, Mutex, OnceCell}; use tracing::instrument; diff --git a/core/startos/src/service/service_effect_handler.rs b/core/startos/src/service/service_effect_handler.rs index 0ad45aad1..c5e5433e6 100644 --- a/core/startos/src/service/service_effect_handler.rs +++ b/core/startos/src/service/service_effect_handler.rs @@ -16,7 +16,7 @@ use models::{ ActionId, DataUrl, HealthCheckId, HostId, Id, ImageId, PackageId, ServiceInterfaceId, VolumeId, }; use patch_db::json_ptr::JsonPointer; -use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn, from_fn_async, Context, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::process::Command; use ts_rs::TS; @@ -36,7 +36,7 @@ use crate::net::service_interface::{ ServiceInterfaceWithHostInfo, }; use crate::prelude::*; -use crate::s9pk::merkle_archive::source::http::{HttpReader, HttpSource}; +use crate::s9pk::merkle_archive::source::http::HttpSource; use crate::s9pk::rpc::SKIP_ENV; use crate::s9pk::S9pk; use crate::service::cli::ContainerCliContext; @@ -74,14 +74,17 @@ struct RpcData { method: String, params: Value, } -pub fn service_effect_handler() -> ParentHandler { +pub fn service_effect_handler() -> ParentHandler { ParentHandler::new() - .subcommand("gitInfo", from_fn(crate::version::git_info)) + .subcommand("gitInfo", from_fn(|_: C| crate::version::git_info())) .subcommand( "echo", - from_fn(echo).with_remote_cli::(), + from_fn(echo::).with_call_remote::(), + ) + .subcommand( + "chroot", + from_fn(chroot::).no_display(), ) - .subcommand("chroot", from_fn(chroot).no_display()) .subcommand("exists", from_fn_async(exists).no_cli()) .subcommand("executeAction", from_fn_async(execute_action).no_cli()) .subcommand("getConfigured", from_fn_async(get_configured).no_cli()) @@ -89,35 +92,35 @@ pub fn service_effect_handler() -> ParentHandler { "stopped", from_fn_async(stopped) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "running", from_fn_async(running) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "restart", from_fn_async(restart) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "shutdown", from_fn_async(shutdown) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "setConfigured", from_fn_async(set_configured) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "setMainStatus", - from_fn_async(set_main_status).with_remote_cli::(), + from_fn_async(set_main_status).with_call_remote::(), ) .subcommand("setHealth", from_fn_async(set_health).no_cli()) .subcommand("getStore", from_fn_async(get_store).no_cli()) @@ -129,10 +132,8 @@ pub fn service_effect_handler() -> ParentHandler { .subcommand( "createOverlayedImage", from_fn_async(create_overlayed_image) - .with_custom_display_fn::(|_, (path, _)| { - Ok(println!("{}", path.display())) - }) - .with_remote_cli::(), + .with_custom_display_fn(|_, (path, _)| Ok(println!("{}", path.display()))) + .with_call_remote::(), ) .subcommand( "destroyOverlayedImage", @@ -154,19 +155,19 @@ pub fn service_effect_handler() -> ParentHandler { "setDependencies", from_fn_async(set_dependencies) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "getDependencies", from_fn_async(get_dependencies) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "checkDependencies", from_fn_async(check_dependencies) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand("getSystemSmtp", from_fn_async(get_system_smtp).no_cli()) .subcommand("getContainerIp", from_fn_async(get_container_ip).no_cli()) @@ -493,7 +494,7 @@ struct GetHostInfoParams { callback: Callback, } async fn get_host_info( - _: AnyContext, + _: EffectContext, GetHostInfoParams { .. }: GetHostInfoParams, ) -> Result { todo!() @@ -537,7 +538,7 @@ struct GetServiceInterfaceParams { callback: Callback, } async fn get_service_interface( - _: AnyContext, + _: EffectContext, GetServiceInterfaceParams { callback, package_id, @@ -584,8 +585,8 @@ struct ChrootParams { #[ts(type = "string[]")] args: Vec, } -fn chroot( - _: AnyContext, +fn chroot( + _: C, ChrootParams { env, workdir, @@ -734,7 +735,7 @@ async fn set_store( let model = db .as_private_mut() .as_package_stores_mut() - .upsert(&package_id, || Box::new(json!({})))?; + .upsert(&package_id, || json!({}))?; let mut model_value = model.de()?; if model_value.is_null() { model_value = json!({}); diff --git a/core/startos/src/service/service_map.rs b/core/startos/src/service/service_map.rs index 891741754..02b0538ec 100644 --- a/core/startos/src/service/service_map.rs +++ b/core/startos/src/service/service_map.rs @@ -165,7 +165,7 @@ impl ServiceMap { configured: false, main: MainStatus::Stopped, }, - marketplace_url: None, + registry: None, developer_key: Pem::new(developer_key), icon, last_backup: None, @@ -206,6 +206,7 @@ impl ServiceMap { Some(Duration::from_millis(100)), ))); + download_progress.start(); let mut progress_writer = ProgressTrackerWriter::new( crate::util::io::create_file(&download_path).await?, download_progress, @@ -229,6 +230,7 @@ impl ServiceMap { .await?; Ok(reload_guard .handle_last(async move { + finalization_progress.start(); let s9pk = S9pk::open(&installed_path, Some(&id), true).await?; let prev = if let Some(service) = service.take() { ensure_code!( @@ -246,7 +248,6 @@ impl ServiceMap { service .uninstall(Some(s9pk.as_manifest().version.clone())) .await?; - finalization_progress.complete(); progress_handle.complete(); Some(version) } else { diff --git a/core/startos/src/setup.rs b/core/startos/src/setup.rs index 9120544e3..a035e932f 100644 --- a/core/startos/src/setup.rs +++ b/core/startos/src/setup.rs @@ -7,7 +7,7 @@ use josekit::jwk::Jwk; use openssl::x509::X509; use patch_db::json_ptr::ROOT; use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{from_fn_async, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tokio::fs::File; use tokio::io::AsyncWriteExt; @@ -37,7 +37,7 @@ use crate::util::crypto::EncryptedWire; use crate::util::io::{dir_copy, dir_size, Counter}; use crate::{Error, ErrorKind, ResultExt}; -pub fn setup() -> ParentHandler { +pub fn setup() -> ParentHandler { ParentHandler::new() .subcommand( "status", @@ -45,10 +45,10 @@ pub fn setup() -> ParentHandler { .with_metadata("authenticated", Value::Bool(false)) .no_cli(), ) - .subcommand("disk", disk()) + .subcommand("disk", disk::()) .subcommand("attach", from_fn_async(attach).no_cli()) .subcommand("execute", from_fn_async(execute).no_cli()) - .subcommand("cifs", cifs()) + .subcommand("cifs", cifs::()) .subcommand("complete", from_fn_async(complete).no_cli()) .subcommand( "get-pubkey", @@ -59,7 +59,7 @@ pub fn setup() -> ParentHandler { .subcommand("exit", from_fn_async(exit).no_cli()) } -pub fn disk() -> ParentHandler { +pub fn disk() -> ParentHandler { ParentHandler::new().subcommand( "list", from_fn_async(list_disks) @@ -207,7 +207,7 @@ pub async fn get_pubkey(ctx: SetupContext) -> Result { Ok(pub_key) } -pub fn cifs() -> ParentHandler { +pub fn cifs() -> ParentHandler { ParentHandler::new().subcommand("verify", from_fn_async(verify_cifs).no_cli()) } @@ -360,7 +360,7 @@ pub async fn complete(ctx: SetupContext) -> Result { crate::ErrorKind::InvalidRequest, )); }; - let mut guid_file = File::create("/media/embassy/config/disk.guid").await?; + let mut guid_file = File::create("/media/startos/config/disk.guid").await?; guid_file.write_all(guid.as_bytes()).await?; guid_file.sync_all().await?; Ok(setup_result) @@ -463,7 +463,7 @@ async fn migrate( let _ = crate::disk::main::import( &old_guid, - "/media/embassy/migrate", + "/media/startos/migrate", RepairStrategy::Preen, if guid.ends_with("_UNENC") { None @@ -473,9 +473,9 @@ async fn migrate( ) .await?; - let main_transfer_args = ("/media/embassy/migrate/main/", "/embassy-data/main/"); + let main_transfer_args = ("/media/startos/migrate/main/", "/embassy-data/main/"); let package_data_transfer_args = ( - "/media/embassy/migrate/package-data/", + "/media/startos/migrate/package-data/", "/embassy-data/package-data/", ); @@ -540,7 +540,7 @@ async fn migrate( let (hostname, tor_addr, root_ca) = setup_init(&ctx, Some(start_os_password)).await?; - crate::disk::main::export(&old_guid, "/media/embassy/migrate").await?; + crate::disk::main::export(&old_guid, "/media/startos/migrate").await?; Ok((guid, hostname, tor_addr, root_ca)) } diff --git a/core/startos/src/ssh.rs b/core/startos/src/ssh.rs index 787d54056..82d06be4c 100644 --- a/core/startos/src/ssh.rs +++ b/core/startos/src/ssh.rs @@ -5,7 +5,7 @@ use clap::builder::ValueParserFactory; use clap::Parser; use color_eyre::eyre::eyre; use imbl_value::InternedString; -use rpc_toolkit::{command, from_fn_async, AnyContext, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use tracing::instrument; use ts_rs::TS; @@ -79,28 +79,28 @@ impl std::str::FromStr for SshPubKey { } // #[command(subcommands(add, delete, list,))] -pub fn ssh() -> ParentHandler { +pub fn ssh() -> ParentHandler { ParentHandler::new() .subcommand( "add", from_fn_async(add) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "delete", from_fn_async(delete) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "list", from_fn_async(list) .with_display_serializable() - .with_custom_display_fn::(|handle, result| { + .with_custom_display_fn(|handle, result| { Ok(display_all_ssh_keys(handle.params, result)) }) - .with_remote_cli::(), + .with_call_remote::(), ) } diff --git a/core/startos/src/system.rs b/core/startos/src/system.rs index 1a851fd2e..50b17b54a 100644 --- a/core/startos/src/system.rs +++ b/core/startos/src/system.rs @@ -5,8 +5,7 @@ use chrono::Utc; use clap::Parser; use color_eyre::eyre::eyre; use futures::FutureExt; -use rpc_toolkit::yajrc::RpcError; -use rpc_toolkit::{command, from_fn_async, AnyContext, Empty, HandlerExt, ParentHandler}; +use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use tokio::process::Command; use tokio::sync::broadcast::Receiver; @@ -16,33 +15,30 @@ use ts_rs::TS; use crate::context::{CliContext, RpcContext}; use crate::disk::util::{get_available, get_used}; -use crate::logs::{ - cli_logs_generic_follow, cli_logs_generic_nofollow, fetch_logs, follow_logs, LogFollowResponse, - LogResponse, LogSource, -}; +use crate::logs::{LogSource, LogsParams, SYSTEM_UNIT}; use crate::prelude::*; +use crate::rpc_continuations::RpcContinuations; use crate::shutdown::Shutdown; use crate::util::cpupower::{get_available_governors, set_governor, Governor}; use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; use crate::util::Invoke; -use crate::{Error, ErrorKind, ResultExt}; -pub fn experimental() -> ParentHandler { +pub fn experimental() -> ParentHandler { ParentHandler::new() .subcommand( "zram", from_fn_async(zram) .no_display() - .with_remote_cli::(), + .with_call_remote::(), ) .subcommand( "governor", from_fn_async(governor) .with_display_serializable() - .with_custom_display_fn::(|handle, result| { + .with_custom_display_fn(|handle, result| { Ok(display_governor_info(handle.params, result)) }) - .with_remote_cli::(), + .with_call_remote::(), ) } @@ -230,173 +226,13 @@ pub async fn time(ctx: RpcContext, _: Empty) -> Result { uptime: ctx.start_time.elapsed().as_secs(), }) } -#[derive(Deserialize, Serialize, Parser, TS)] -#[serde(rename_all = "camelCase")] -#[command(rename_all = "kebab-case")] -pub struct LogsParams { - #[arg(short = 'l', long = "limit")] - #[ts(type = "number | null")] - limit: Option, - #[arg(short = 'c', long = "cursor")] - cursor: Option, - #[arg(short = 'B', long = "before")] - #[serde(default)] - before: bool, - #[arg(short = 'f', long = "follow")] - #[serde(default)] - follow: bool, + +pub fn logs>() -> ParentHandler { + crate::logs::logs(|_: &C, _| async { Ok(LogSource::Unit(SYSTEM_UNIT)) }) } -pub fn logs() -> ParentHandler { - ParentHandler::new() - .root_handler( - from_fn_async(cli_logs) - .no_display() - .with_inherited(|params, _| params), - ) - .root_handler( - from_fn_async(logs_nofollow) - .with_inherited(|params, _| params) - .no_cli(), - ) - .subcommand( - "follow", - from_fn_async(logs_follow) - .with_inherited(|params, _| params) - .no_cli(), - ) -} - -pub async fn cli_logs( - ctx: CliContext, - _: Empty, - LogsParams { - limit, - cursor, - before, - follow, - }: LogsParams, -) -> Result<(), RpcError> { - if follow { - if cursor.is_some() { - return Err(RpcError::from(Error::new( - eyre!("The argument '--cursor ' cannot be used with '--follow'"), - crate::ErrorKind::InvalidRequest, - ))); - } - if before { - return Err(RpcError::from(Error::new( - eyre!("The argument '--before' cannot be used with '--follow'"), - crate::ErrorKind::InvalidRequest, - ))); - } - cli_logs_generic_follow(ctx, "server.logs.follow", None, limit).await - } else { - cli_logs_generic_nofollow(ctx, "server.logs", None, limit, cursor, before).await - } -} -pub async fn logs_nofollow( - _ctx: AnyContext, - _: Empty, - LogsParams { - limit, - cursor, - before, - .. - }: LogsParams, -) -> Result { - fetch_logs(LogSource::System, limit, cursor, before).await -} - -pub async fn logs_follow( - ctx: RpcContext, - _: Empty, - LogsParams { limit, .. }: LogsParams, -) -> Result { - follow_logs(ctx, LogSource::System, limit).await -} -#[derive(Deserialize, Serialize, Parser, TS)] -#[serde(rename_all = "camelCase")] -#[command(rename_all = "kebab-case")] -pub struct KernelLogsParams { - #[arg(short = 'l', long = "limit")] - #[ts(type = "number | null")] - limit: Option, - #[arg(short = 'c', long = "cursor")] - cursor: Option, - #[arg(short = 'B', long = "before")] - #[serde(default)] - before: bool, - #[arg(short = 'f', long = "follow")] - #[serde(default)] - follow: bool, -} -pub fn kernel_logs() -> ParentHandler { - ParentHandler::new() - .root_handler( - from_fn_async(cli_kernel_logs) - .no_display() - .with_inherited(|params, _| params), - ) - .root_handler( - from_fn_async(kernel_logs_nofollow) - .with_inherited(|params, _| params) - .no_cli(), - ) - .subcommand( - "follow", - from_fn_async(kernel_logs_follow) - .with_inherited(|params, _| params) - .no_cli(), - ) -} -pub async fn cli_kernel_logs( - ctx: CliContext, - _: Empty, - KernelLogsParams { - limit, - cursor, - before, - follow, - }: KernelLogsParams, -) -> Result<(), RpcError> { - if follow { - if cursor.is_some() { - return Err(RpcError::from(Error::new( - eyre!("The argument '--cursor ' cannot be used with '--follow'"), - crate::ErrorKind::InvalidRequest, - ))); - } - if before { - return Err(RpcError::from(Error::new( - eyre!("The argument '--before' cannot be used with '--follow'"), - crate::ErrorKind::InvalidRequest, - ))); - } - cli_logs_generic_follow(ctx, "server.kernel-logs.follow", None, limit).await - } else { - cli_logs_generic_nofollow(ctx, "server.kernel-logs", None, limit, cursor, before).await - } -} -pub async fn kernel_logs_nofollow( - _ctx: AnyContext, - _: Empty, - KernelLogsParams { - limit, - cursor, - before, - .. - }: KernelLogsParams, -) -> Result { - fetch_logs(LogSource::Kernel, limit, cursor, before).await -} - -pub async fn kernel_logs_follow( - ctx: RpcContext, - _: Empty, - KernelLogsParams { limit, .. }: KernelLogsParams, -) -> Result { - follow_logs(ctx, LogSource::Kernel, limit).await +pub fn kernel_logs>() -> ParentHandler { + crate::logs::logs(|_: &C, _| async { Ok(LogSource::Kernel) }) } #[derive(Serialize, Deserialize)] diff --git a/core/startos/src/update/latest_information.rs b/core/startos/src/update/latest_information.rs deleted file mode 100644 index e897dd40a..000000000 --- a/core/startos/src/update/latest_information.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::collections::HashMap; - -use emver::Version; -use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, DisplayFromStr}; - -#[serde_as] -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct LatestInformation { - release_notes: HashMap, - headline: String, - #[serde_as(as = "DisplayFromStr")] - pub version: Version, -} - -/// Captured from https://beta-registry-0-3.start9labs.com/eos/latest 2021-09-24 -#[test] -fn latest_information_from_server() { - let data_from_server = r#"{"release-notes":{"0.3.0":"This major software release encapsulates the optimal performance, security, and management enhancments to the embassyOS experience."},"headline":"Major embassyOS release","version":"0.3.0"}"#; - let latest_information: LatestInformation = serde_json::from_str(data_from_server).unwrap(); - assert_eq!(latest_information.version.minor(), 3); -} diff --git a/core/startos/src/update/mod.rs b/core/startos/src/update/mod.rs index 05303182d..5bcf8445d 100644 --- a/core/startos/src/update/mod.rs +++ b/core/startos/src/update/mod.rs @@ -1,46 +1,60 @@ -use std::path::PathBuf; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::collections::BTreeMap; +use std::path::Path; +use std::time::Duration; -use clap::Parser; +use clap::{ArgAction, Parser}; use color_eyre::eyre::{eyre, Result}; -use emver::Version; -use helpers::{Rsync, RsyncOptions}; -use lazy_static::lazy_static; +use emver::{Version, VersionRange}; +use futures::{FutureExt, TryStreamExt}; +use helpers::{AtomicFile, NonDetachingJoinHandle}; +use imbl_value::json; +use itertools::Itertools; +use patch_db::json_ptr::JsonPointer; use reqwest::Url; -use rpc_toolkit::command; +use rpc_toolkit::HandlerArgs; use serde::{Deserialize, Serialize}; use tokio::process::Command; -use tokio_stream::StreamExt; use tracing::instrument; use ts_rs::TS; -use crate::context::RpcContext; -use crate::db::model::public::UpdateProgress; -use crate::disk::mount::filesystem::bind::Bind; -use crate::disk::mount::filesystem::ReadWrite; -use crate::disk::mount::guard::MountGuard; +use crate::context::{CliContext, RpcContext}; use crate::notifications::{notify, NotificationLevel}; use crate::prelude::*; -use crate::registry::marketplace::with_query_params; +use crate::progress::{ + FullProgressTracker, FullProgressTrackerHandle, PhaseProgressTrackerHandle, PhasedProgressBar, +}; +use crate::registry::asset::RegistryAsset; +use crate::registry::context::{RegistryContext, RegistryUrlParams}; +use crate::registry::os::index::OsVersionInfo; +use crate::registry::signer::FileValidator; +use crate::rpc_continuations::{RequestGuid, RpcContinuation}; +use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; use crate::sound::{ CIRCLE_OF_5THS_SHORT, UPDATE_FAILED_1, UPDATE_FAILED_2, UPDATE_FAILED_3, UPDATE_FAILED_4, }; -use crate::update::latest_information::LatestInformation; use crate::util::Invoke; -use crate::{Error, ErrorKind, ResultExt, PLATFORM}; - -mod latest_information; - -lazy_static! { - static ref UPDATED: AtomicBool = AtomicBool::new(false); -} +use crate::PLATFORM; #[derive(Deserialize, Serialize, Parser, TS)] #[serde(rename_all = "camelCase")] #[command(rename_all = "kebab-case")] pub struct UpdateSystemParams { #[ts(type = "string")] - marketplace_url: Url, + registry: Url, + #[ts(type = "string | null")] + #[arg(long = "to")] + target: Option, + #[arg(long = "no-progress", action = ArgAction::SetFalse)] + #[serde(default)] + progress: bool, +} + +#[derive(Deserialize, Serialize, TS)] +pub struct UpdateSystemRes { + #[ts(type = "string | null")] + target: Option, + #[ts(type = "string | null")] + progress: Option, } /// An user/ daemon would call this to update the system to the latest version and do the updates available, @@ -48,16 +62,137 @@ pub struct UpdateSystemParams { #[instrument(skip_all)] pub async fn update_system( ctx: RpcContext, - UpdateSystemParams { marketplace_url }: UpdateSystemParams, -) -> Result { - if UPDATED.load(Ordering::SeqCst) { - return Ok(UpdateResult::NoUpdates); + UpdateSystemParams { + target, + registry, + progress, + }: UpdateSystemParams, +) -> Result { + if ctx + .db + .peek() + .await + .into_public() + .into_server_info() + .into_status_info() + .into_updated() + .de()? + { + return Err(Error::new(eyre!("Server was already updated. Please restart your device before attempting to update again."), ErrorKind::InvalidRequest)); } - Ok(if maybe_do_update(ctx, marketplace_url).await?.is_some() { - UpdateResult::Updating + let target = + maybe_do_update(ctx.clone(), registry, target.unwrap_or(VersionRange::Any)).await?; + let progress = if progress && target.is_some() { + let guid = RequestGuid::new(); + ctx.clone() + .rpc_continuations + .add( + guid.clone(), + RpcContinuation::ws( + Box::new(|mut ws| { + async move { + if let Err(e) = async { + let mut sub = ctx + .db + .subscribe( + "/public/serverInfo/statusInfo/updateProgress" + .parse::() + .with_kind(ErrorKind::Database)?, + ) + .await; + while { + let progress = ctx + .db + .peek() + .await + .into_public() + .into_server_info() + .into_status_info() + .into_update_progress() + .de()?; + ws.send(axum::extract::ws::Message::Text( + serde_json::to_string(&progress) + .with_kind(ErrorKind::Serialization)?, + )) + .await + .with_kind(ErrorKind::Network)?; + progress.is_some() + } { + sub.recv().await; + } + + ws.close().await.with_kind(ErrorKind::Network)?; + + Ok::<_, Error>(()) + } + .await + { + tracing::error!("Error returning progress of update: {e}"); + tracing::debug!("{e:?}") + } + } + .boxed() + }), + Duration::from_secs(30), + ), + ) + .await; + Some(guid) } else { - UpdateResult::NoUpdates - }) + None + }; + Ok(UpdateSystemRes { target, progress }) +} + +pub async fn cli_update_system( + HandlerArgs { + context, + parent_method, + method, + raw_params, + .. + }: HandlerArgs, +) -> Result<(), Error> { + let res = from_value::( + context + .call_remote::( + &parent_method.into_iter().chain(method).join("."), + raw_params, + ) + .await?, + )?; + match res.target { + None => println!("No updates available"), + Some(v) => { + if let Some(progress) = res.progress { + let mut ws = context.ws_continuation(progress).await?; + let mut progress = PhasedProgressBar::new(&format!("Updating to v{v}...")); + let mut prev = None; + while let Some(msg) = ws.try_next().await.with_kind(ErrorKind::Network)? { + if let tokio_tungstenite::tungstenite::Message::Text(msg) = msg { + if let Some(snap) = + serde_json::from_str(&msg).with_kind(ErrorKind::Deserialization)? + { + progress.update(&snap); + prev = Some(snap); + } else { + break; + } + } + } + if let Some(mut prev) = prev { + for phase in &mut prev.phases { + phase.progress.complete(); + } + prev.overall.complete(); + progress.update(&prev); + } + } else { + println!("Updating to v{v}...") + } + } + } + Ok(()) } /// What is the status of the updates? @@ -80,30 +215,49 @@ pub fn display_update_result(_: UpdateSystemParams, status: UpdateResult) { } #[instrument(skip_all)] -async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result, Error> { +async fn maybe_do_update( + ctx: RpcContext, + registry: Url, + target: VersionRange, +) -> Result, Error> { let peeked = ctx.db.peek().await; - let latest_version: Version = ctx - .client - .get(with_query_params( - ctx.clone(), - format!("{}/eos/v0/latest", marketplace_url,).parse()?, - )) - .send() - .await - .with_kind(ErrorKind::Network)? - .json::() - .await - .with_kind(ErrorKind::Network)? - .version; let current_version = peeked.as_public().as_server_info().as_version().de()?; - if latest_version < *current_version { + let mut available = from_value::>( + ctx.call_remote_with::( + "os.version.get", + json!({ + "source": current_version, + "target": target, + }), + RegistryUrlParams { registry }, + ) + .await?, + )?; + let Some((target_version, asset)) = available + .pop_last() + .and_then(|(v, mut info)| info.squashfs.remove(&**PLATFORM).map(|a| (v, a))) + else { return Ok(None); + }; + if !target_version.satisfies(&target) { + return Err(Error::new( + eyre!("got back version from registry that does not satisfy {target}"), + ErrorKind::Registry, + )); } - let eos_url = EosUrl { - base: marketplace_url, - version: latest_version, - }; + let validator = asset.validate(asset.signature_info.all_signers())?; + + let mut progress = FullProgressTracker::new(); + let progress_handle = progress.handle(); + let mut download_phase = progress_handle.add_phase("Downloading File".into(), Some(100)); + download_phase.set_total(validator.size()?); + let reverify_phase = progress_handle.add_phase("Reverifying File".into(), Some(10)); + let sync_boot_phase = progress_handle.add_phase("Syncing Boot Files".into(), Some(1)); + let finalize_phase = progress_handle.add_phase("Finalizing Update".into(), Some(1)); + + let start_progress = progress.snapshot(); + let status = ctx .db .mutate(|db| { @@ -115,10 +269,7 @@ async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result