diff --git a/build/README.md b/build/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/build/dpkg-deps/depends b/build/dpkg-deps/depends index 00625f41b..9491a1d48 100644 --- a/build/dpkg-deps/depends +++ b/build/dpkg-deps/depends @@ -3,6 +3,7 @@ avahi-utils b3sum bash-completion beep +binfmt-support bmon btrfs-progs ca-certificates @@ -15,6 +16,7 @@ dnsutils dosfstools e2fsprogs ecryptfs-utils +equivs exfatprogs flashrom fuse3 diff --git a/build/dpkg-deps/generate.sh b/build/dpkg-deps/generate.sh index 7f1c2dcae..ffb80dce3 100755 --- a/build/dpkg-deps/generate.sh +++ b/build/dpkg-deps/generate.sh @@ -9,6 +9,9 @@ FEATURES+=("${ARCH}") if [ "$ARCH" != "$PLATFORM" ]; then FEATURES+=("${PLATFORM}") fi +if [[ "$PLATFORM" =~ -nonfree$ ]]; then + FEATURES+=("nonfree") +fi feature_file_checker=' /^#/ { next } diff --git a/build/dpkg-deps/nonfree.depends b/build/dpkg-deps/nonfree.depends new file mode 100644 index 000000000..73d021d02 --- /dev/null +++ b/build/dpkg-deps/nonfree.depends @@ -0,0 +1,10 @@ ++ firmware-amd-graphics ++ firmware-atheros ++ firmware-brcm80211 ++ firmware-iwlwifi ++ firmware-libertas ++ firmware-misc-nonfree ++ firmware-realtek ++ nvidia-container-toolkit +# + nvidia-driver +# + nvidia-kernel-dkms \ No newline at end of file diff --git a/build/image-recipe/build.sh b/build/image-recipe/build.sh index e2bd0b116..641f16c7c 100755 --- a/build/image-recipe/build.sh +++ b/build/image-recipe/build.sh @@ -73,7 +73,7 @@ if [ "$NON_FREE" = 1 ]; then if [ "$IB_SUITE" = "bullseye" ]; then ARCHIVE_AREAS="$ARCHIVE_AREAS non-free" else - ARCHIVE_AREAS="$ARCHIVE_AREAS non-free-firmware" + ARCHIVE_AREAS="$ARCHIVE_AREAS non-free non-free-firmware" fi fi @@ -174,40 +174,123 @@ if [ "${IB_TARGET_PLATFORM}" = "rockchip64" ]; then echo "deb https://apt.armbian.com/ ${IB_SUITE} main" > config/archives/armbian.list fi -cat > config/archives/backports.pref <<- EOF +if [ "$NON_FREE" = 1 ]; then + curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | gpg --dearmor -o config/archives/nvidia-container-toolkit.key + curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \ + | sed 's#deb https://#deb [signed-by=/etc/apt/trusted.gpg.d/nvidia-container-toolkit.key.gpg] https://#g' \ + > config/archives/nvidia-container-toolkit.list +fi + +cat > config/archives/backports.pref <<-EOF Package: linux-image-* Pin: release n=${IB_SUITE}-backports Pin-Priority: 500 + +Package: linux-headers-* +Pin: release n=${IB_SUITE}-backports +Pin-Priority: 500 + +Package: *nvidia* +Pin: release n=${IB_SUITE}-backports +Pin-Priority: 500 EOF -# Dependencies - -## Firmware -if [ "$NON_FREE" = 1 ]; then - echo 'firmware-iwlwifi firmware-misc-nonfree firmware-brcm80211 firmware-realtek firmware-atheros firmware-libertas firmware-amd-graphics' > config/package-lists/nonfree.list.chroot -fi +# Hooks cat > config/hooks/normal/9000-install-startos.hook.chroot << EOF #!/bin/bash set -e +if [ "${NON_FREE}" = "1" ] && [ "${IB_TARGET_PLATFORM}" != "raspberrypi" ]; then + # install a specific NVIDIA driver version + + # ---------------- configuration ---------------- + NVIDIA_DRIVER_VERSION="\${NVIDIA_DRIVER_VERSION:-580.119.02}" + + BASE_URL="https://download.nvidia.com/XFree86/Linux-${QEMU_ARCH}" + + echo "[nvidia-hook] Using NVIDIA driver: \${NVIDIA_DRIVER_VERSION}" >&2 + + # ---------------- kernel version ---------------- + + # Determine target kernel version from newest /boot/vmlinuz-* in the chroot. + KVER="\$( + ls -1t /boot/vmlinuz-* 2>/dev/null \ + | head -n1 \ + | sed 's|.*/vmlinuz-||' + )" + + if [ -z "\${KVER}" ]; then + echo "[nvidia-hook] ERROR: no /boot/vmlinuz-* found; cannot determine kernel version" >&2 + exit 1 + fi + + echo "[nvidia-hook] Target kernel version: \${KVER}" >&2 + + # Ensure kernel headers are present + TEMP_APT_DEPS=(build-essential) + if [ ! -e "/lib/modules/\${KVER}/build" ]; then + TEMP_APT_DEPS+=(linux-headers-\${KVER}) + fi + + echo "[nvidia-hook] Installing build dependencies" >&2 + + /usr/lib/startos/scripts/install-equivs <<-EOF + Package: nvidia-depends + Version: \${NVIDIA_DRIVER_VERSION} + Section: unknown + Priority: optional + Depends: \${dep_list="\$(IFS=', '; echo "\${TEMP_APT_DEPS[*]}")"} + EOF + + # ---------------- download and run installer ---------------- + + RUN_NAME="NVIDIA-Linux-${QEMU_ARCH}-\${NVIDIA_DRIVER_VERSION}.run" + RUN_PATH="/root/\${RUN_NAME}" + RUN_URL="\${BASE_URL}/\${NVIDIA_DRIVER_VERSION}/\${RUN_NAME}" + + echo "[nvidia-hook] Downloading \${RUN_URL}" >&2 + wget -O "\${RUN_PATH}" "\${RUN_URL}" + chmod +x "\${RUN_PATH}" + + echo "[nvidia-hook] Running NVIDIA installer for kernel \${KVER}" >&2 + + sh "\${RUN_PATH}" \ + --silent \ + --kernel-name="\${KVER}" \ + --no-x-check \ + --no-nouveau-check \ + --no-runlevel-check + + # Rebuild module metadata + echo "[nvidia-hook] Running depmod for \${KVER}" >&2 + depmod -a "\${KVER}" + + echo "[nvidia-hook] NVIDIA \${NVIDIA_DRIVER_VERSION} installation complete for kernel \${KVER}" >&2 + + echo "[nvidia-hook] Removing build dependencies..." >&2 + apt-get purge -y nvidia-depends + apt-get autoremove -y + echo "[nvidia-hook] Removed build dependencies." >&2 +fi + cp /etc/resolv.conf /etc/resolv.conf.bak if [ "${IB_SUITE}" = trixie ] && [ "${IB_TARGET_ARCH}" != riscv64 ]; then - echo 'deb https://deb.debian.org/debian/ bookworm main' > /etc/apt/sources.list.d/bookworm.list - apt-get update - apt-get install -y postgresql-15 - rm /etc/apt/sources.list.d/bookworm.list - apt-get update - systemctl mask postgresql + echo 'deb https://deb.debian.org/debian/ bookworm main' > /etc/apt/sources.list.d/bookworm.list + apt-get update + apt-get install -y postgresql-15 + rm /etc/apt/sources.list.d/bookworm.list + apt-get update + systemctl mask postgresql fi if [ "${IB_TARGET_PLATFORM}" = "raspberrypi" ]; then - ln -sf /usr/bin/pi-beep /usr/local/bin/beep - KERNEL_VERSION=${RPI_KERNEL_VERSION} sh /boot/config.sh > /boot/config.txt - mkinitramfs -c gzip -o initrd.img-${RPI_KERNEL_VERSION}-rpi-v8 ${RPI_KERNEL_VERSION}-rpi-v8 - mkinitramfs -c gzip -o initrd.img-${RPI_KERNEL_VERSION}-rpi-2712 ${RPI_KERNEL_VERSION}-rpi-2712 + ln -sf /usr/bin/pi-beep /usr/local/bin/beep + KERNEL_VERSION=${RPI_KERNEL_VERSION} sh /boot/config.sh > /boot/config.txt + mkinitramfs -c gzip -o initrd.img-${RPI_KERNEL_VERSION}-rpi-v8 ${RPI_KERNEL_VERSION}-rpi-v8 + mkinitramfs -c gzip -o initrd.img-${RPI_KERNEL_VERSION}-rpi-2712 ${RPI_KERNEL_VERSION}-rpi-2712 fi useradd --shell /bin/bash -G startos -m start9 @@ -218,11 +301,11 @@ usermod -aG systemd-journal start9 echo "start9 ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee "/etc/sudoers.d/010_start9-nopasswd" if [ "${IB_TARGET_PLATFORM}" != "raspberrypi" ]; then - /usr/lib/startos/scripts/enable-kiosk + /usr/lib/startos/scripts/enable-kiosk fi if ! [[ "${IB_OS_ENV}" =~ (^|-)dev($|-) ]]; then - passwd -l start9 + passwd -l start9 fi EOF @@ -360,4 +443,4 @@ elif [ "${IMAGE_TYPE}" = img ]; then fi -chown $IB_UID:$IB_UID $RESULTS_DIR/$IMAGE_BASENAME.* \ No newline at end of file +chown $IB_UID:$IB_UID $RESULTS_DIR/$IMAGE_BASENAME.* diff --git a/build/image-recipe/raspberrypi/squashfs/boot/cmdline.txt b/build/image-recipe/raspberrypi/squashfs/boot/cmdline.txt index 315dc67a7..f10c50da5 100644 --- a/build/image-recipe/raspberrypi/squashfs/boot/cmdline.txt +++ b/build/image-recipe/raspberrypi/squashfs/boot/cmdline.txt @@ -1 +1 @@ -usb-storage.quirks=152d:0562:u,14cd:121c:u,0781:cfcb:u console=serial0,115200 console=tty1 root=PARTUUID=cb15ae4d-02 rootfstype=ext4 fsck.repair=yes rootwait cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory quiet boot=startos \ No newline at end of file +usb-storage.quirks=152d:0562:u,14cd:121c:u,0781:cfcb:u console=serial0,115200 console=tty1 root=PARTUUID=cb15ae4d-02 rootfstype=ext4 fsck.repair=yes rootwait cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory boot=startos \ No newline at end of file diff --git a/build/lib/motd b/build/lib/motd index 69b443364..fef88f66d 100755 --- a/build/lib/motd +++ b/build/lib/motd @@ -4,7 +4,7 @@ parse_essential_db_info() { DB_DUMP="/tmp/startos_db.json" if command -v start-cli >/dev/null 2>&1; then - start-cli db dump > "$DB_DUMP" 2>/dev/null || return 1 + timeout 30 start-cli db dump > "$DB_DUMP" 2>/dev/null || return 1 else return 1 fi diff --git a/build/lib/scripts/install-equivs b/build/lib/scripts/install-equivs new file mode 100755 index 000000000..610881093 --- /dev/null +++ b/build/lib/scripts/install-equivs @@ -0,0 +1,20 @@ +#!/bin/bash + +export DEBIAN_FRONTEND=noninteractive +export DEBCONF_NONINTERACTIVE_SEEN=true + +TMP_DIR=$(mktemp -d) + +( + set -e + cd $TMP_DIR + + cat > control.equivs + equivs-build control.equivs + apt-get install -y ./*.deb < /dev/null +) + +rm -rf $TMP_DIR + +echo Install complete. >&2 +exit 0 \ No newline at end of file diff --git a/build/raspberrypi/make-image.sh b/build/raspberrypi/make-image.sh deleted file mode 100755 index ec5ea4297..000000000 --- a/build/raspberrypi/make-image.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash - -set -e - -function partition_for () { - if [[ "$1" =~ [0-9]+$ ]]; then - echo "$1p$2" - else - echo "$1$2" - fi -} - -VERSION=$(cat VERSION.txt) -ENVIRONMENT=$(cat ENVIRONMENT.txt) -GIT_HASH=$(cat GIT_HASH.txt | head -c 7) -DATE=$(date +%Y%m%d) - -ROOT_PART_END=7217792 - -VERSION_FULL="$VERSION-$GIT_HASH" - -if [ -n "$ENVIRONMENT" ]; then - VERSION_FULL="$VERSION_FULL~$ENVIRONMENT" -fi - -TARGET_NAME=startos-${VERSION_FULL}-${DATE}_raspberrypi.img -TARGET_SIZE=$[($ROOT_PART_END+1)*512] - -rm -f $TARGET_NAME -truncate -s $TARGET_SIZE $TARGET_NAME -( - echo o - echo x - echo i - echo "0xcb15ae4d" - echo r - echo n - echo p - echo 1 - echo 2048 - echo 526335 - echo t - echo c - echo n - echo p - echo 2 - echo 526336 - echo $ROOT_PART_END - echo a - echo 1 - echo w -) | fdisk $TARGET_NAME -OUTPUT_DEVICE=$(sudo losetup --show -fP $TARGET_NAME) -sudo mkfs.ext4 `partition_for ${OUTPUT_DEVICE} 2` -sudo mkfs.vfat `partition_for ${OUTPUT_DEVICE} 1` - -TMPDIR=$(mktemp -d) - -sudo mount `partition_for ${OUTPUT_DEVICE} 2` $TMPDIR -sudo mkdir $TMPDIR/boot -sudo mount `partition_for ${OUTPUT_DEVICE} 1` $TMPDIR/boot -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=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 -sudo umount $TMPDIR -sudo losetup -d $OUTPUT_DEVICE - -if [ "$ALLOW_VERSION_MISMATCH" != 1 ]; then - if [ "$(cat GIT_HASH.txt)" != "$REAL_GIT_HASH" ]; then - >&2 echo "startos.raspberrypi.squashfs GIT_HASH.txt mismatch" - >&2 echo "expected $REAL_GIT_HASH (dpkg) found $(cat GIT_HASH.txt) (repo)" - exit 1 - fi - if [ "$(cat VERSION.txt)" != "$REAL_VERSION" ]; then - >&2 echo "startos.raspberrypi.squashfs VERSION.txt mismatch" - exit 1 - fi - if [ "$(cat ENVIRONMENT.txt)" != "$REAL_ENVIRONMENT" ]; then - >&2 echo "startos.raspberrypi.squashfs ENVIRONMENT.txt mismatch" - exit 1 - fi -fi \ No newline at end of file diff --git a/build/upload-ota.sh b/build/upload-ota.sh index 164ca8878..be36e52f6 100755 --- a/build/upload-ota.sh +++ b/build/upload-ota.sh @@ -15,10 +15,10 @@ if [ "$SKIP_DL" != "1" ]; then fi if [ -n "$RUN_ID" ]; then - for arch in aarch64 aarch64-nonfree riscv64 x86_64 x86_64-nonfree raspberrypi; do + for arch in aarch64 aarch64-nonfree riscv64 riscv64-nonfree x86_64 x86_64-nonfree raspberrypi; do while ! gh run download -R Start9Labs/start-os $RUN_ID -n $arch.squashfs -D $(pwd); do sleep 1; done done - for arch in aarch64 aarch64-nonfree riscv64 x86_64 x86_64-nonfree; do + for arch in aarch64 aarch64-nonfree riscv64 riscv64-nonfree x86_64 x86_64-nonfree; do while ! gh run download -R Start9Labs/start-os $RUN_ID -n $arch.iso -D $(pwd); do sleep 1; done done while ! gh run download -R Start9Labs/start-os $RUN_ID -n raspberrypi.img -D $(pwd); do sleep 1; done @@ -69,7 +69,7 @@ elif [ "$SKIP_UL" != "1" ]; then fi if [ "$SKIP_INDEX" != "1" ]; then - for arch in aarch64 aarch64-nonfree x86_64 x86_64-nonfree; do + for arch in aarch64 aarch64-nonfree riscv64 riscv64-nonfree x86_64 x86_64-nonfree; do for file in *_$arch.squashfs *_$arch.iso; do start-cli --registry=https://alpha-registry-x.start9.com registry os asset add --platform=$arch --version=$VERSION $file https://github.com/Start9Labs/start-os/releases/download/v$VERSION/$(echo -n "$file" | sed 's/~/./g') done diff --git a/core/Cargo.lock b/core/Cargo.lock index f4b9cf48d..4dcc5641e 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -48,7 +48,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "version_check", ] @@ -104,7 +104,7 @@ dependencies = [ "amplify_derive", "amplify_num", "ascii", - "getrandom 0.2.16", + "getrandom 0.2.17", "getrandom 0.3.4", "wasm-bindgen", ] @@ -272,7 +272,7 @@ dependencies = [ "cfg-if", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "fs-mistrust", "futures", @@ -367,7 +367,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] @@ -379,7 +379,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] @@ -391,7 +391,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -413,7 +413,7 @@ dependencies = [ "pem", "rcgen", "ring", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pemfile", "serde", "serde_json", @@ -459,13 +459,12 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "futures-io", "pin-project-lite", "tokio", @@ -513,16 +512,16 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.1.2", + "rustix 1.1.3", "slab", "windows-sys 0.61.2", ] [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener 5.4.1", "event-listener-strategy", @@ -556,7 +555,7 @@ dependencies = [ "cfg-if", "event-listener 5.4.1", "futures-lite", - "rustix 1.1.2", + "rustix 1.1.3", ] [[package]] @@ -567,7 +566,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -582,7 +581,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.1.2", + "rustix 1.1.3", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -634,7 +633,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -651,7 +650,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -721,9 +720,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.2" +version = "1.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +checksum = "e84ce723ab67259cfeb9877c6a639ee9eb7a27b28123abd71db7f0d5d0cc9d86" dependencies = [ "aws-lc-sys", "zeroize", @@ -731,9 +730,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +checksum = "43a442ece363113bd4bd4c8b18977a7798dd4d3c3383f34fb61936960e8f4ad8" dependencies = [ "cc", "cmake", @@ -743,9 +742,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "base64 0.22.1", @@ -779,9 +778,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -869,9 +868,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "basic-cookies" @@ -996,26 +995,27 @@ dependencies = [ [[package]] name = "blake2b_simd" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" dependencies = [ "arrayref", "arrayvec 0.7.6", - "constant_time_eq 0.3.1", + "constant_time_eq", ] [[package]] name = "blake3" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" dependencies = [ "arrayref", "arrayvec 0.7.6", "cc", "cfg-if", - "constant_time_eq 0.3.1", + "constant_time_eq", + "cpufeatures", "memmap2 0.9.9", "rayon-core", ] @@ -1028,7 +1028,7 @@ checksum = "e0b121a9fe0df916e362fb3271088d071159cdf11db0e4182d02152850756eff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1162,9 +1162,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.49" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", "jobserver", @@ -1198,9 +1198,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -1269,9 +1269,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -1279,9 +1279,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -1299,14 +1299,14 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "clipboard-win" @@ -1329,9 +1329,9 @@ dependencies = [ [[package]] name = "coarsetime" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4" +checksum = "e58eb270476aa4fc7843849f8a35063e8743b4dbcdf6dd0f8ea0886980c204c2" dependencies = [ "libc", "wasix", @@ -1392,9 +1392,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.35" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" dependencies = [ "brotli", "compression-core", @@ -1499,12 +1499,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - [[package]] name = "constant_time_eq" version = "0.4.2" @@ -1761,12 +1755,12 @@ checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ "bitflags 2.10.0", "crossterm_winapi", - "derive_more 2.1.0", + "derive_more 2.1.1", "document-features", "futures-core", "mio", "parking_lot 0.12.5", - "rustix 1.1.2", + "rustix 1.1.3", "signal-hook", "signal-hook-mio", "winapi", @@ -1904,7 +1898,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1952,7 +1946,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1974,14 +1968,14 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "der" @@ -2031,7 +2025,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2061,14 +2055,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea41269bd490d251b9eca50ccb43117e641cc68b129849757c15ece88fe0574" dependencies = [ "heck 0.5.0", - "indexmap 2.12.1", + "indexmap 2.13.0", "itertools 0.14.0", "proc-macro-crate", "proc-macro2", "quote", "sha3 0.10.8", "strum", - "syn 2.0.111", + "syn 2.0.114", "void", ] @@ -2080,7 +2074,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2124,29 +2118,29 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "derive_more" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case 0.10.0", "proc-macro2", "quote", "rustc_version", - "syn 2.0.111", + "syn 2.0.114", "unicode-xid", ] @@ -2236,7 +2230,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2317,7 +2311,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2505,7 +2499,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2518,7 +2512,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2539,7 +2533,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2616,7 +2610,7 @@ dependencies = [ "either", "emver", "fp-core", - "getrandom 0.2.16", + "getrandom 0.2.17", "itertools 0.13.0", "memchr", "pest", @@ -2717,9 +2711,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" [[package]] name = "fixed-capacity-vec" @@ -2735,9 +2729,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -2952,7 +2946,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2973,7 +2967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", ] @@ -3069,9 +3063,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -3155,9 +3149,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -3165,7 +3159,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.12.1", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -3263,7 +3257,7 @@ dependencies = [ "dynasmrt", "fixed-capacity-vec", "hex", - "rand_core 0.9.3", + "rand_core 0.9.5", "thiserror 2.0.17", ] @@ -3529,12 +3523,12 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", "tower-service", - "webpki-roots 1.0.4", + "webpki-roots 1.0.5", ] [[package]] @@ -3767,7 +3761,7 @@ dependencies = [ "archery", "bitmaps", "imbl-sized-chunks", - "rand_core 0.9.3", + "rand_core 0.9.5", "rand_xoshiro", "serde", "version_check", @@ -3844,9 +3838,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -3950,9 +3944,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -4038,9 +4032,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jaq-core" @@ -4315,7 +4309,7 @@ dependencies = [ "nom 8.0.0", "percent-encoding", "quoted_printable", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-platform-verifier", "socket2 0.6.1", "tokio", @@ -4338,9 +4332,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libloading" @@ -4390,13 +4384,13 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.6.0", + "redox_syscall 0.7.0", ] [[package]] @@ -4602,7 +4596,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4650,9 +4644,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.11" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -4660,7 +4654,6 @@ dependencies = [ "equivalent", "parking_lot 0.12.5", "portable-atomic", - "rustc_version", "smallvec", "tagptr", "uuid", @@ -4685,7 +4678,7 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", "security-framework 2.11.1", @@ -4816,9 +4809,9 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -4957,7 +4950,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5135,7 +5128,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5144,6 +5137,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "openssl-src" version = "300.5.4+3.5.4" @@ -5382,9 +5381,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", "ucd-trie", @@ -5392,9 +5391,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -5402,22 +5401,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "pest_meta" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2 0.10.9", @@ -5430,7 +5429,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.12.1", + "indexmap 2.13.0", ] [[package]] @@ -5484,7 +5483,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5497,7 +5496,7 @@ dependencies = [ "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5541,7 +5540,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5601,7 +5600,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", - "indexmap 2.12.1", + "indexmap 2.13.0", "quick-xml", "serde", "time", @@ -5645,15 +5644,15 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "portable-pty" @@ -5771,7 +5770,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93980406f12d9f8140ed5abe7155acb10bb1e69ea55c88960b9c2f117445ef96" dependencies = [ "equivalent", - "indexmap 2.12.1", + "indexmap 2.13.0", "serde", ] @@ -5803,14 +5802,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -5825,7 +5824,7 @@ dependencies = [ "chrono", "flate2", "procfs-core", - "rustix 1.1.2", + "rustix 1.1.3", ] [[package]] @@ -5866,14 +5865,14 @@ checksum = "fb6dc647500e84a25a85b100e76c85b8ace114c209432dc174f20aac11d4ed6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "prost" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", "prost-derive", @@ -5881,22 +5880,22 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "prost-types" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" dependencies = [ "prost", ] @@ -5923,7 +5922,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71cec9e2670207c5ebb9e477763c74436af3b9091dd550b9fb3c1bec7f3ea266" dependencies = [ - "rustix 1.1.2", + "rustix 1.1.3", ] [[package]] @@ -5993,7 +5992,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.35", + "rustls 0.23.36", "socket2 0.6.1", "thiserror 2.0.17", "tokio", @@ -6013,7 +6012,7 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", "slab", "thiserror 2.0.17", @@ -6038,9 +6037,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -6149,7 +6148,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -6179,7 +6178,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -6197,14 +6196,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -6225,7 +6224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16df48f071248e67b8fc5e866d9448d45c08ad8b672baaaf796e2f15e606ff0" dependencies = [ "libc", - "rand_core 0.9.3", + "rand_core 0.9.5", "winapi", ] @@ -6235,7 +6234,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -6244,7 +6243,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -6308,9 +6307,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ "bitflags 2.10.0", ] @@ -6321,7 +6320,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 1.0.69", ] @@ -6332,7 +6331,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 2.0.17", ] @@ -6354,7 +6353,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -6394,9 +6393,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.26" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", @@ -6420,7 +6419,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", "serde", "serde_json", @@ -6438,7 +6437,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.4", + "webpki-roots 1.0.5", ] [[package]] @@ -6482,7 +6481,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -6502,7 +6501,7 @@ dependencies = [ [[package]] name = "rpc-toolkit" version = "0.3.2" -source = "git+https://github.com/Start9Labs/rpc-toolkit.git#81d18147fd0ca9725b820c010c006e8a2cada322" +source = "git+https://github.com/Start9Labs/rpc-toolkit.git#406ee9e88bf20e3155f150eb755b5b9c2aefd167" dependencies = [ "async-stream", "async-trait", @@ -6530,9 +6529,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest 0.10.7", @@ -6582,7 +6581,7 @@ checksum = "8ae76b7506744d254fd0eb2c0ff5c5d108201ccbb083111ac04a44eeda105680" dependencies = [ "base64 0.22.1", "blake2b_simd", - "constant_time_eq 0.4.2", + "constant_time_eq", "crossbeam-utils", ] @@ -6631,9 +6630,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", @@ -6658,9 +6657,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "log", @@ -6674,11 +6673,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe", + "openssl-probe 0.2.0", "rustls-pki-types", "schannel", "security-framework 3.5.1", @@ -6714,7 +6713,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki 0.103.8", @@ -6773,16 +6772,16 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "safelog" version = "0.4.8" source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" dependencies = [ - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "either", "fluid-let", @@ -6830,9 +6829,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -6986,7 +6985,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -7001,16 +7000,16 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -7043,7 +7042,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -7086,9 +7085,9 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.1", + "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.1.0", + "schemars 1.2.0", "serde_core", "serde_json", "serde_with_macros", @@ -7104,7 +7103,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -7113,7 +7112,7 @@ version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "itoa", "libyml", "memchr", @@ -7267,10 +7266,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -7483,12 +7483,12 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink", - "indexmap 2.12.1", + "indexmap 2.13.0", "log", "memchr", "once_cell", "percent-encoding", - "rustls 0.23.35", + "rustls 0.23.36", "serde", "serde_json", "sha2 0.10.9", @@ -7511,7 +7511,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -7532,7 +7532,7 @@ dependencies = [ "sha2 0.10.9", "sqlx-core", "sqlx-postgres", - "syn 2.0.111", + "syn 2.0.114", "tokio", "url", ] @@ -7597,7 +7597,7 @@ dependencies = [ "quote", "regex-syntax 0.6.29", "strsim 0.11.1", - "syn 2.0.111", + "syn 2.0.114", "unicode-width 0.1.14", ] @@ -7715,7 +7715,7 @@ dependencies = [ "imbl", "imbl-value 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "include_dir", - "indexmap 2.12.1", + "indexmap 2.13.0", "indicatif", "inotify", "integer-encoding", @@ -7786,7 +7786,7 @@ dependencies = [ "tokio-tar", "tokio-tungstenite 0.26.2", "tokio-util", - "toml 0.9.9+spec-1.0.0", + "toml 0.9.11+spec-1.1.0", "tor-cell", "tor-hscrypto", "tor-hsservice", @@ -7886,7 +7886,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -7929,9 +7929,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -7955,7 +7955,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -8039,14 +8039,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -8076,7 +8076,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.60.2", ] @@ -8127,7 +8127,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -8138,7 +8138,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -8152,30 +8152,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", @@ -8228,9 +8228,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -8252,7 +8252,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -8282,15 +8282,15 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.35", + "rustls 0.23.36", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -8340,9 +8340,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -8375,14 +8375,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.9+spec-1.0.0" +version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5238e643fc34a1d5d7e753e1532a91912d74b63b92b3ea51fde8d1b7bc79dd" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "serde_core", "serde_spanned 1.0.4", - "toml_datetime 0.7.4+spec-1.0.0", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow", @@ -8399,9 +8399,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.4+spec-1.0.0" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe3cea6b2aa3b910092f6abd4053ea464fab5f9c170ba5e9a6aead16ec4af2b6" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] @@ -8412,7 +8412,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -8426,17 +8426,17 @@ version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.12.1", - "toml_datetime 0.7.4+spec-1.0.0", + "indexmap 2.13.0", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.5+spec-1.0.0" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c03bee5ce3696f31250db0bbaff18bc43301ce0e8db2ed1f07cbb2acf89984c" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] @@ -8449,9 +8449,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "toml_writer" -version = "1.0.5+spec-1.0.0" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9cd6190959dce0994aa8970cd32ab116d1851ead27e866039acaf2524ce44fa" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" @@ -8513,7 +8513,7 @@ name = "tor-basic-utils" version = "0.33.0" source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" dependencies = [ - "derive_more 2.1.0", + "derive_more 2.1.1", "hex", "itertools 0.14.0", "libc", @@ -8553,7 +8553,7 @@ dependencies = [ "bytes", "caret", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "itertools 0.14.0", "paste", @@ -8580,7 +8580,7 @@ source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit dependencies = [ "caret", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "thiserror 2.0.17", "tor-bytes", @@ -8596,7 +8596,7 @@ dependencies = [ "async-trait", "caret", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "futures", "oneshot-fused-workaround", @@ -8645,7 +8645,7 @@ dependencies = [ "cfg-if", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "downcast-rs 2.0.2", "dyn-clone", "educe", @@ -8707,7 +8707,7 @@ dependencies = [ "serde_ignored", "strum", "thiserror 2.0.17", - "toml 0.9.9+spec-1.0.0", + "toml 0.9.11+spec-1.1.0", "tor-basic-utils", "tor-error", "tor-rtcompat", @@ -8746,7 +8746,7 @@ source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit dependencies = [ "async-compression", "base64ct", - "derive_more 2.1.0", + "derive_more 2.1.1", "futures", "hex", "http", @@ -8774,7 +8774,7 @@ dependencies = [ "async-trait", "base64ct", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "educe", "event-listener 5.4.1", @@ -8824,7 +8824,7 @@ name = "tor-error" version = "0.33.0" source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" dependencies = [ - "derive_more 2.1.0", + "derive_more 2.1.1", "futures", "paste", "retry-error", @@ -8841,7 +8841,7 @@ version = "0.33.0" source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" dependencies = [ "arbitrary", - "derive_more 2.1.0", + "derive_more 2.1.1", "thiserror 2.0.17", "void", ] @@ -8855,7 +8855,7 @@ dependencies = [ "base64ct", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "dyn-clone", "educe", "futures", @@ -8894,7 +8894,7 @@ source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit dependencies = [ "async-trait", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "either", "futures", @@ -8938,7 +8938,7 @@ dependencies = [ "cipher 0.4.4", "data-encoding", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "equix", "hex", @@ -8973,7 +8973,7 @@ dependencies = [ "cfg-if", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "educe", "fs-mistrust", @@ -8987,7 +8987,7 @@ dependencies = [ "oneshot-fused-workaround", "postage", "rand 0.9.2", - "rand_core 0.9.3", + "rand_core 0.9.5", "retry-error", "safelog", "serde", @@ -9025,7 +9025,7 @@ version = "0.33.0" source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" dependencies = [ "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "downcast-rs 2.0.2", "paste", "rand 0.9.2", @@ -9049,7 +9049,7 @@ dependencies = [ "cfg-if", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "downcast-rs 2.0.2", "dyn-clone", "fs-mistrust", @@ -9088,7 +9088,7 @@ dependencies = [ "caret", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "hex", "itertools 0.14.0", "safelog", @@ -9115,7 +9115,7 @@ dependencies = [ "curve25519-dalek 4.1.3", "der-parser 10.0.0", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "ed25519-dalek 2.2.0", "educe", @@ -9124,7 +9124,7 @@ dependencies = [ "rand 0.9.2", "rand_chacha 0.9.0", "rand_core 0.6.4", - "rand_core 0.9.3", + "rand_core 0.9.5", "rand_jitter", "rdrand", "rsa", @@ -9163,7 +9163,7 @@ source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit dependencies = [ "cfg-if", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "dyn-clone", "educe", "futures", @@ -9192,7 +9192,7 @@ source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit dependencies = [ "async-trait", "bitflags 2.10.0", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "futures", "hex", @@ -9228,7 +9228,7 @@ dependencies = [ "cipher 0.4.4", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "educe", "hex", @@ -9270,7 +9270,7 @@ source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit dependencies = [ "amplify", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "filetime", "fs-mistrust", "fslock", @@ -9307,7 +9307,7 @@ dependencies = [ "criterion-cycles-per-byte", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "educe", "futures", @@ -9319,7 +9319,7 @@ dependencies = [ "pin-project", "postage", "rand 0.9.2", - "rand_core 0.9.3", + "rand_core 0.9.5", "safelog", "slotmap-careful", "smallvec", @@ -9391,7 +9391,7 @@ dependencies = [ "async_executors", "asynchronous-codec", "coarsetime", - "derive_more 2.1.0", + "derive_more 2.1.1", "dyn-clone", "educe", "futures", @@ -9421,7 +9421,7 @@ dependencies = [ "assert_matches", "async-trait", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "futures", "humantime", @@ -9462,7 +9462,7 @@ version = "0.33.0" source = "git+https://github.com/Start9Labs/arti.git?branch=patch%2Fdisable-exit#24730694701a83432d791d80802db8bda0699700" dependencies = [ "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "serde", "thiserror 2.0.17", "tor-memquota", @@ -9490,13 +9490,13 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.12.1", + "indexmap 2.13.0", "pin-project-lite", "slab", "sync_wrapper", @@ -9539,9 +9539,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -9569,14 +9569,14 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -9650,7 +9650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -9677,7 +9677,7 @@ checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "termcolor", ] @@ -9734,7 +9734,7 @@ checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -9787,9 +9787,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-bidi" @@ -9868,14 +9868,15 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -9946,7 +9947,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -9969,7 +9970,7 @@ checksum = "2a3bfb04fd13da4fc8df24709b7a0949667f43c63691d9fecddf1d3be8af5099" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -10044,9 +10045,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasix" -version = "0.12.21" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +checksum = "1757e0d1f8456693c7e5c6c629bdb54884e032aa0bb53c155f6a39f94440d332" dependencies = [ "wasi 0.11.1+wasi-snapshot-preview1", ] @@ -10096,7 +10097,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -10223,9 +10224,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" dependencies = [ "rustls-pki-types", ] @@ -10236,14 +10237,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.4", + "webpki-roots 1.0.5", ] [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -10383,7 +10384,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -10394,7 +10395,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -10903,7 +10904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.1.2", + "rustix 1.1.3", ] [[package]] @@ -10993,15 +10994,15 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] [[package]] name = "zbus" -version = "5.12.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" +checksum = "17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b" dependencies = [ "async-broadcast", "async-executor", @@ -11017,8 +11018,9 @@ dependencies = [ "futures-core", "futures-lite", "hex", - "nix 0.30.1", + "libc", "ordered-stream", + "rustix 1.1.3", "serde", "serde_repr", "tracing", @@ -11033,14 +11035,14 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.12.0" +version = "5.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" +checksum = "aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "zbus_names", "zvariant", "zvariant_utils", @@ -11048,34 +11050,33 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.2.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "static_assertions", "winnow", "zvariant", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -11095,7 +11096,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] @@ -11110,13 +11111,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -11150,9 +11151,15 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] +[[package]] +name = "zmij" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" + [[package]] name = "zstd" version = "0.13.3" @@ -11183,9 +11190,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.8.0" +version = "5.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" +checksum = "326aaed414f04fe839777b4c443d4e94c74e7b3621093bd9c5e649ac8aa96543" dependencies = [ "endi", "enumflags2", @@ -11197,26 +11204,26 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.8.0" +version = "5.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" +checksum = "ba44e1f8f4da9e6e2d25d2a60b116ef8b9d0be174a7685e55bb12a99866279a7" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.111", + "syn 2.0.114", "winnow", ] diff --git a/core/src/bins/mod.rs b/core/src/bins/mod.rs index 7f4e7da7f..de809e093 100644 --- a/core/src/bins/mod.rs +++ b/core/src/bins/mod.rs @@ -11,67 +11,89 @@ pub mod startd; pub mod tunnel; #[derive(Default)] -pub struct MultiExecutable(BTreeMap<&'static str, fn(VecDeque)>); +pub struct MultiExecutable { + default: Option<&'static str>, + bins: BTreeMap<&'static str, fn(VecDeque)>, +} impl MultiExecutable { pub fn enable_startd(&mut self) -> &mut Self { - self.0.insert("startd", startd::main); - self.0 + self.bins.insert("startd", startd::main); + self.bins .insert("embassyd", |_| deprecated::renamed("embassyd", "startd")); - self.0 + self.bins .insert("embassy-init", |_| deprecated::removed("embassy-init")); self } pub fn enable_start_cli(&mut self) -> &mut Self { - self.0.insert("start-cli", start_cli::main); - self.0.insert("embassy-cli", |_| { + self.bins.insert("start-cli", start_cli::main); + self.bins.insert("embassy-cli", |_| { deprecated::renamed("embassy-cli", "start-cli") }); - self.0 + self.bins .insert("embassy-sdk", |_| deprecated::removed("embassy-sdk")); self } pub fn enable_start_container(&mut self) -> &mut Self { - self.0.insert("start-container", container_cli::main); + self.bins.insert("start-container", container_cli::main); self } pub fn enable_start_registryd(&mut self) -> &mut Self { - self.0.insert("start-registryd", registry::main); + self.bins.insert("start-registryd", registry::main); self } pub fn enable_start_registry(&mut self) -> &mut Self { - self.0.insert("start-registry", registry::cli); + self.bins.insert("start-registry", registry::cli); self } pub fn enable_start_tunneld(&mut self) -> &mut Self { - self.0.insert("start-tunneld", tunnel::main); + self.bins.insert("start-tunneld", tunnel::main); self } pub fn enable_start_tunnel(&mut self) -> &mut Self { - self.0.insert("start-tunnel", tunnel::cli); + self.bins.insert("start-tunnel", tunnel::cli); + self + } + + pub fn set_default(&mut self, name: &str) -> &mut Self { + if let Some((name, _)) = self.bins.get_key_value(name) { + self.default = Some(*name); + } else { + panic!("{name} does not exist in MultiExecutable"); + } self } fn select_executable(&self, name: &str) -> Option)> { - self.0.get(&name).copied() + self.bins.get(&name).copied() } pub fn execute(&self) { + let mut popped = Vec::with_capacity(2); let mut args = std::env::args_os().collect::>(); + for _ in 0..2 { if let Some(s) = args.pop_front() { if let Some(name) = Path::new(&*s).file_name().and_then(|s| s.to_str()) { if name == "--contents" { - for name in self.0.keys() { + for name in self.bins.keys() { println!("{name}"); } + return; } if let Some(x) = self.select_executable(&name) { args.push_front(s); return x(args); } } + popped.push(s); } } + if let Some(default) = self.default { + while let Some(arg) = popped.pop() { + args.push_front(arg); + } + return self.bins[default](args); + } let args = std::env::args().collect::>(); eprintln!( "unknown executable: {}", diff --git a/core/src/context/cli.rs b/core/src/context/cli.rs index aafbc796e..5aeda762e 100644 --- a/core/src/context/cli.rs +++ b/core/src/context/cli.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use cookie::{Cookie, Expiration, SameSite}; use cookie_store::CookieStore; use http::HeaderMap; +use imbl::OrdMap; use imbl_value::InternedString; use josekit::jwk::Jwk; use once_cell::sync::OnceCell; @@ -238,10 +239,16 @@ impl CliContext { where Self: CallRemote, { - >::call_remote(&self, method, params, Empty {}) - .await - .map_err(Error::from) - .with_ctx(|e| (e.kind, method)) + >::call_remote( + &self, + method, + OrdMap::new(), + params, + Empty {}, + ) + .await + .map_err(Error::from) + .with_ctx(|e| (e.kind, method)) } pub async fn call_remote_with( &self, @@ -252,10 +259,16 @@ impl CliContext { where Self: CallRemote, { - >::call_remote(&self, method, params, extra) - .await - .map_err(Error::from) - .with_ctx(|e| (e.kind, method)) + >::call_remote( + &self, + method, + OrdMap::new(), + params, + extra, + ) + .await + .map_err(Error::from) + .with_ctx(|e| (e.kind, method)) } } impl AsRef for CliContext { @@ -292,7 +305,13 @@ impl AsRef for CliContext { } impl CallRemote for CliContext { - async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { + async fn call_remote( + &self, + method: &str, + _: OrdMap<&'static str, Value>, + params: Value, + _: Empty, + ) -> Result { if let Ok(local) = read_file_to_string(RpcContext::LOCAL_AUTH_COOKIE_PATH).await { self.cookie_store .lock() @@ -319,7 +338,13 @@ impl CallRemote for CliContext { } } impl CallRemote for CliContext { - async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { + async fn call_remote( + &self, + method: &str, + _: OrdMap<&'static str, Value>, + params: Value, + _: Empty, + ) -> Result { crate::middleware::auth::signature::call_remote( self, self.rpc_url.clone(), @@ -332,7 +357,13 @@ impl CallRemote for CliContext { } } impl CallRemote for CliContext { - async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { + async fn call_remote( + &self, + method: &str, + _: OrdMap<&'static str, Value>, + params: Value, + _: Empty, + ) -> Result { crate::middleware::auth::signature::call_remote( self, self.rpc_url.clone(), @@ -345,7 +376,13 @@ impl CallRemote for CliContext { } } impl CallRemote for CliContext { - async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { + async fn call_remote( + &self, + method: &str, + _: OrdMap<&'static str, Value>, + params: Value, + _: Empty, + ) -> Result { crate::middleware::auth::signature::call_remote( self, self.rpc_url.clone(), @@ -358,7 +395,13 @@ impl CallRemote for CliContext { } } impl CallRemote for CliContext { - async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { + async fn call_remote( + &self, + method: &str, + _: OrdMap<&'static str, Value>, + params: Value, + _: Empty, + ) -> Result { crate::middleware::auth::signature::call_remote( self, self.rpc_url.clone(), diff --git a/core/src/context/rpc.rs b/core/src/context/rpc.rs index f8be8af19..6988e5b75 100644 --- a/core/src/context/rpc.rs +++ b/core/src/context/rpc.rs @@ -15,6 +15,7 @@ use josekit::jwk::Jwk; use reqwest::{Client, Proxy}; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{CallRemote, Context, Empty}; +use tokio::process::Command; use tokio::sync::{RwLock, broadcast, oneshot, watch}; use tokio::time::Instant; use tracing::instrument; @@ -26,6 +27,10 @@ use crate::context::config::ServerConfig; use crate::db::model::Database; use crate::db::model::package::TaskSeverity; use crate::disk::OsPartitionInfo; +use crate::disk::mount::filesystem::bind::Bind; +use crate::disk::mount::filesystem::block_dev::BlockDev; +use crate::disk::mount::filesystem::{FileSystem, ReadOnly}; +use crate::disk::mount::guard::MountGuard; use crate::init::{InitResult, check_time_is_synchronized}; use crate::install::PKG_ARCHIVE_DIR; use crate::lxc::LxcManager; @@ -41,12 +46,14 @@ use crate::rpc_continuations::{Guid, OpenAuthedContinuations, RpcContinuations}; use crate::service::ServiceMap; use crate::service::action::update_tasks; use crate::service::effects::callbacks::ServiceCallbacks; +use crate::service::effects::subcontainer::NVIDIA_OVERLAY_PATH; use crate::shutdown::Shutdown; +use crate::util::Invoke; use crate::util::future::NonDetachingJoinHandle; -use crate::util::io::delete_file; +use crate::util::io::{TmpDir, delete_file}; use crate::util::lshw::LshwDevice; use crate::util::sync::{SyncMutex, SyncRwLock, Watch}; -use crate::{ActionId, DATA_DIR, PackageId}; +use crate::{ActionId, DATA_DIR, PLATFORM, PackageId}; pub struct RpcContextSeed { is_closed: AtomicBool, @@ -167,6 +174,124 @@ impl RpcContext { init_net_ctrl.complete(); tracing::info!("Initialized Net Controller"); + if PLATFORM.ends_with("-nonfree") { + if let Err(e) = Command::new("nvidia-smi") + .invoke(ErrorKind::ParseSysInfo) + .await + { + tracing::warn!("nvidia-smi: {e}"); + tracing::info!("The above warning can be ignored if no NVIDIA card is present"); + } else { + async { + let version: InternedString = String::from_utf8( + Command::new("modinfo") + .arg("-F") + .arg("version") + .arg("nvidia") + .invoke(ErrorKind::ParseSysInfo) + .await?, + )? + .trim() + .into(); + + let nvidia_dir = + Path::new("/media/startos/data/package-data/nvidia").join(&*version); + + // Generate single squashfs with both debian and generic overlays + let sqfs = nvidia_dir.join("container-overlay.squashfs"); + if tokio::fs::metadata(&sqfs).await.is_err() { + let tmp = TmpDir::new().await?; + + // Generate debian overlay (libs in /usr/lib/aarch64-linux-gnu/) + let debian_dir = tmp.join("debian"); + tokio::fs::create_dir_all(&debian_dir).await?; + // Create /etc/debian_version to trigger debian path detection + tokio::fs::create_dir_all(debian_dir.join("etc")).await?; + tokio::fs::write(debian_dir.join("etc/debian_version"), "").await?; + let procfs = MountGuard::mount( + &Bind::new("/proc"), + debian_dir.join("proc"), + ReadOnly, + ) + .await?; + Command::new("nvidia-container-cli") + .arg("configure") + .arg("--no-devbind") + .arg("--no-cgroups") + .arg("--utility") + .arg("--compute") + .arg("--graphics") + .arg("--video") + .arg(&debian_dir) + .invoke(ErrorKind::Unknown) + .await?; + procfs.unmount(true).await?; + // Run ldconfig to create proper symlinks for all NVIDIA libraries + Command::new("ldconfig") + .arg("-r") + .arg(&debian_dir) + .invoke(ErrorKind::Unknown) + .await?; + // Remove /etc/debian_version - it was only needed for nvidia-container-cli detection + tokio::fs::remove_file(debian_dir.join("etc/debian_version")).await?; + + // Generate generic overlay (libs in /usr/lib64/) + let generic_dir = tmp.join("generic"); + tokio::fs::create_dir_all(&generic_dir).await?; + // No /etc/debian_version - will use generic /usr/lib64 paths + let procfs = MountGuard::mount( + &Bind::new("/proc"), + generic_dir.join("proc"), + ReadOnly, + ) + .await?; + Command::new("nvidia-container-cli") + .arg("configure") + .arg("--no-devbind") + .arg("--no-cgroups") + .arg("--utility") + .arg("--compute") + .arg("--graphics") + .arg("--video") + .arg(&generic_dir) + .invoke(ErrorKind::Unknown) + .await?; + procfs.unmount(true).await?; + // Run ldconfig to create proper symlinks for all NVIDIA libraries + Command::new("ldconfig") + .arg("-r") + .arg(&generic_dir) + .invoke(ErrorKind::Unknown) + .await?; + + // Create squashfs with UID/GID mapping (avoids chown on readonly mounts) + if let Some(p) = sqfs.parent() { + tokio::fs::create_dir_all(p) + .await + .with_ctx(|_| (ErrorKind::Filesystem, format!("mkdir -p {p:?}")))?; + } + Command::new("mksquashfs") + .arg(&*tmp) + .arg(&sqfs) + .arg("-force-uid") + .arg("100000") + .arg("-force-gid") + .arg("100000") + .invoke(ErrorKind::Filesystem) + .await?; + tmp.unmount_and_delete().await?; + } + BlockDev::new(&sqfs) + .mount(NVIDIA_OVERLAY_PATH, ReadOnly) + .await?; + + Ok::<_, Error>(()) + } + .await + .log_err(); + } + } + let services = ServiceMap::default(); let metrics_cache = Watch::>::new(None); let socks_proxy_url = format!("socks5h://{socks_proxy}"); @@ -460,8 +585,14 @@ impl RpcContext { where Self: CallRemote, { - >::call_remote(&self, method, params, Empty {}) - .await + >::call_remote( + &self, + method, + OrdMap::new(), + params, + Empty {}, + ) + .await } pub async fn call_remote_with( &self, @@ -472,7 +603,14 @@ impl RpcContext { where Self: CallRemote, { - >::call_remote(&self, method, params, extra).await + >::call_remote( + &self, + method, + OrdMap::new(), + params, + extra, + ) + .await } } impl AsRef for RpcContext { diff --git a/core/src/db/prelude.rs b/core/src/db/prelude.rs index f45d051cc..70d10c470 100644 --- a/core/src/db/prelude.rs +++ b/core/src/db/prelude.rs @@ -416,6 +416,51 @@ impl Model { } } +impl Model +where + T::Key: FromStr, + Error: From<::Err>, +{ + /// Retains only the elements specified by the predicate. + /// The predicate can mutate the values and returns whether to keep each entry. + pub fn retain(&mut self, mut f: F) -> Result<(), Error> + where + F: FnMut(&T::Key, &mut Model) -> Result, + { + let mut to_remove = Vec::new(); + + match &mut self.value { + Value::Object(o) => { + for (k, v) in o.iter_mut() { + let key = T::Key::from_str(&**k)?; + if !f(&key, patch_db::ModelExt::value_as_mut(v))? { + to_remove.push(k.clone()); + } + } + } + v => { + use serde::de::Error; + return Err(patch_db::value::Error { + source: patch_db::value::ErrorSource::custom(format!( + "expected object found {v}" + )), + kind: patch_db::value::ErrorKind::Deserialization, + } + .into()); + } + } + + // Remove entries that didn't pass the filter + if let Value::Object(o) = &mut self.value { + for k in to_remove { + o.remove(&k); + } + } + + Ok(()) + } +} + #[repr(transparent)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct JsonKey(pub T); diff --git a/core/src/disk/mount/filesystem/overlayfs.rs b/core/src/disk/mount/filesystem/overlayfs.rs index d743589dd..2a3125f0d 100644 --- a/core/src/disk/mount/filesystem/overlayfs.rs +++ b/core/src/disk/mount/filesystem/overlayfs.rs @@ -4,6 +4,7 @@ use std::path::Path; use digest::generic_array::GenericArray; use digest::{Digest, OutputSizeUser}; +use itertools::Itertools; use sha2::Sha256; use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite}; @@ -12,12 +13,13 @@ use crate::prelude::*; use crate::util::io::TmpDir; pub struct OverlayFs, P1: AsRef, P2: AsRef> { - lower: P0, + lower: Vec, upper: P1, work: P2, } impl, P1: AsRef, P2: AsRef> OverlayFs { - pub fn new(lower: P0, upper: P1, work: P2) -> Self { + /// layers are top to bottom + pub fn new(lower: Vec, upper: P1, work: P2) -> Self { Self { lower, upper, work } } } @@ -32,8 +34,10 @@ impl + Send + Sync, P1: AsRef + Send + Sync, P2: AsRef impl IntoIterator { [ - Box::new(lazy_format!("lowerdir={}", self.lower.as_ref().display())) - as Box, + Box::new(lazy_format!( + "lowerdir={}", + self.lower.iter().map(|p| p.as_ref().display()).join(":") + )) as Box, Box::new(lazy_format!("upperdir={}", self.upper.as_ref().display())), Box::new(lazy_format!("workdir={}", self.work.as_ref().display())), ] @@ -51,18 +55,21 @@ impl + Send + Sync, P1: AsRef + Send + Sync, P2: AsRef + Send + Sync, P1: AsRef + Send + Sync, P2: AsRef + Send + Sync, P1: AsRef + Send + Sync, P2: AsRef { inner_guard: MountGuard, } impl OverlayGuard { - pub async fn mount(lower: G, mountpoint: impl AsRef) -> Result { + pub async fn mount_layers>( + pre: &[P], + guard: G, + post: &[P], + mountpoint: impl AsRef, + ) -> Result { let upper = TmpDir::new().await?; let inner_guard = MountGuard::mount( &OverlayFs::new( - lower.path(), + std::iter::empty() + .chain(pre.into_iter().map(|p| p.as_ref())) + .chain([guard.path()]) + .chain(post.into_iter().map(|p| p.as_ref())) + .collect(), upper.as_ref().join("upper"), upper.as_ref().join("work"), ), @@ -111,11 +129,14 @@ impl OverlayGuard { ) .await?; Ok(Self { - lower: Some(lower), + lower: Some(guard), upper: Some(upper), inner_guard, }) } + pub async fn mount(lower: G, mountpoint: impl AsRef) -> Result { + Self::mount_layers::<&Path>(&[], lower, &[], mountpoint).await + } pub async fn unmount(mut self, delete_mountpoint: bool) -> Result<(), Error> { self.inner_guard.take().unmount(delete_mountpoint).await?; if let Some(lower) = self.lower.take() { diff --git a/core/src/disk/mount/util.rs b/core/src/disk/mount/util.rs index 1e0e84952..46fc27890 100644 --- a/core/src/disk/mount/util.rs +++ b/core/src/disk/mount/util.rs @@ -3,6 +3,7 @@ use std::path::Path; use tracing::instrument; use crate::Error; +use crate::prelude::*; use crate::util::Invoke; pub async fn is_mountpoint(path: impl AsRef) -> Result { @@ -56,3 +57,42 @@ pub async fn unmount>(mountpoint: P, lazy: bool) -> Result<(), Er .await?; Ok(()) } + +/// Unmounts all mountpoints under (and including) the given path, in reverse +/// depth order so that nested mounts are unmounted before their parents. +#[instrument(skip_all)] +pub async fn unmount_all_under>(path: P, lazy: bool) -> Result<(), Error> { + let path = path.as_ref(); + let canonical_path = tokio::fs::canonicalize(path) + .await + .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("canonicalize {path:?}")))?; + + let mounts_content = tokio::fs::read_to_string("/proc/mounts") + .await + .with_ctx(|_| (ErrorKind::Filesystem, "read /proc/mounts"))?; + + // Collect all mountpoints under our path + let mut mountpoints: Vec<&str> = mounts_content + .lines() + .filter_map(|line| { + let mountpoint = line.split_whitespace().nth(1)?; + // Check if this mountpoint is under our target path + let mp_path = Path::new(mountpoint); + if mp_path.starts_with(&canonical_path) { + Some(mountpoint) + } else { + None + } + }) + .collect(); + + // Sort by path length descending so we unmount deepest first + mountpoints.sort_by(|a, b| b.len().cmp(&a.len())); + + for mountpoint in mountpoints { + tracing::debug!("Unmounting nested mountpoint: {}", mountpoint); + unmount(mountpoint, lazy).await?; + } + + Ok(()) +} diff --git a/core/src/init.rs b/core/src/init.rs index 37ad4374f..741ca3e2d 100644 --- a/core/src/init.rs +++ b/core/src/init.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use std::time::{Duration, SystemTime}; use axum::extract::ws; -use const_format::formatcp; use futures::{StreamExt, TryStreamExt}; use itertools::Itertools; use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async}; diff --git a/core/src/install/mod.rs b/core/src/install/mod.rs index 7444e5f1b..261bafeb4 100644 --- a/core/src/install/mod.rs +++ b/core/src/install/mod.rs @@ -142,16 +142,16 @@ pub async fn install( .await?, )?; - let asset = &package + let (_, asset) = package .best .get(&version) + .and_then(|i| i.s9pks.first()) .ok_or_else(|| { Error::new( eyre!("{id}@{version} not found on {registry}"), ErrorKind::NotFound, ) - })? - .s9pk; + })?; asset.validate(SIG_CONTEXT, asset.all_signers())?; diff --git a/core/src/lxc/mod.rs b/core/src/lxc/mod.rs index 8091577d2..bdca44ebe 100644 --- a/core/src/lxc/mod.rs +++ b/core/src/lxc/mod.rs @@ -5,11 +5,13 @@ use std::sync::{Arc, Weak}; use std::time::Duration; use clap::builder::ValueParserFactory; -use futures::StreamExt; +use futures::future::BoxFuture; +use futures::{FutureExt, StreamExt}; use imbl_value::InternedString; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{RpcRequest, RpcResponse}; use serde::{Deserialize, Serialize}; +use tokio::fs::ReadDir; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::Command; use tokio::sync::Mutex; @@ -27,7 +29,7 @@ use crate::disk::mount::util::unmount; use crate::prelude::*; use crate::rpc_continuations::{Guid, RpcContinuation}; use crate::service::ServiceStats; -use crate::util::io::open_file; +use crate::util::io::{open_file, write_file_owned_atomic}; use crate::util::rpc_client::UnixRpcClient; use crate::util::{FromStrParser, Invoke, new_guid}; use crate::{InvalidId, PackageId}; @@ -37,6 +39,7 @@ const RPC_DIR: &str = "media/startos/rpc"; // must not be absolute path pub const CONTAINER_RPC_SERVER_SOCKET: &str = "service.sock"; // must not be absolute path pub const HOST_RPC_SERVER_SOCKET: &str = "host.sock"; // must not be absolute path const CONTAINER_DHCP_TIMEOUT: Duration = Duration::from_secs(30); +const HARDWARE_ACCELERATION_PATHS: &[&str] = &["/dev/dri", "/dev/nvidia*", "/dev/kfd"]; #[derive( Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord, Hash, TS, @@ -174,12 +177,8 @@ impl LxcContainer { let machine_id = hex::encode(rand::random::<[u8; 16]>()); let container_dir = Path::new(LXC_CONTAINER_DIR).join(&*guid); tokio::fs::create_dir_all(&container_dir).await?; - tokio::fs::write( - container_dir.join("config"), - format!(include_str!("./config.template"), guid = &*guid), - ) - .await?; - // TODO: append config + let config_str = format!(include_str!("./config.template"), guid = &*guid); + tokio::fs::write(container_dir.join("config"), config_str).await?; let rootfs_dir = container_dir.join("rootfs"); let rootfs = OverlayGuard::mount( TmpMountGuard::mount( @@ -197,8 +196,25 @@ impl LxcContainer { &rootfs_dir, ) .await?; - tokio::fs::write(rootfs_dir.join("etc/machine-id"), format!("{machine_id}\n")).await?; - tokio::fs::write(rootfs_dir.join("etc/hostname"), format!("{guid}\n")).await?; + Command::new("chown") + .arg("100000:100000") + .arg(&rootfs_dir) + .invoke(ErrorKind::Filesystem) + .await?; + write_file_owned_atomic( + rootfs_dir.join("etc/machine-id"), + format!("{machine_id}\n"), + 100000, + 100000, + ) + .await?; + write_file_owned_atomic( + rootfs_dir.join("etc/hostname"), + format!("{guid}\n"), + 100000, + 100000, + ) + .await?; Command::new("sed") .arg("-i") .arg(format!("s/LXC_NAME/{guid}/g")) @@ -248,9 +264,13 @@ impl LxcContainer { .arg("-d") .arg("--name") .arg(&*guid) + .arg("-o") + .arg(format!("/run/startos/LXC_{guid}.log")) + .arg("-l") + .arg("DEBUG") .invoke(ErrorKind::Lxc) .await?; - Ok(Self { + let res = Self { manager: Arc::downgrade(manager), rootfs, guid: Arc::new(ContainerId::try_from(&*guid)?), @@ -258,7 +278,84 @@ impl LxcContainer { config, exited: false, log_mount, - }) + }; + if res.config.hardware_acceleration { + res.handle_devices( + tokio::fs::read_dir("/dev") + .await + .with_ctx(|_| (ErrorKind::Filesystem, "readdir /dev"))?, + HARDWARE_ACCELERATION_PATHS, + ) + .await?; + } + Ok(res) + } + + #[cfg(not(target_os = "linux"))] + async fn handle_devices(&self, _: ReadDir, _: &[&str]) -> Result<(), Error> { + Ok(()) + } + + #[cfg(target_os = "linux")] + fn handle_devices<'a>( + &'a self, + mut dir: ReadDir, + matches: &'a [&'a str], + ) -> BoxFuture<'a, Result<(), Error>> { + use std::os::linux::fs::MetadataExt; + use std::os::unix::fs::FileTypeExt; + async move { + while let Some(ent) = dir.next_entry().await? { + let path = ent.path(); + if let Some(matches) = if matches.is_empty() { + Some(Vec::new()) + } else { + let mut new_matches = Vec::new(); + for mut m in matches.iter().copied() { + let could_match = if let Some(prefix) = m.strip_suffix("*") { + m = prefix; + path.to_string_lossy().starts_with(m) + } else { + path.starts_with(m) + } || Path::new(m).starts_with(&path); + if could_match { + new_matches.push(m); + } + } + if new_matches.is_empty() { + None + } else { + Some(new_matches) + } + } { + let meta = ent.metadata().await?; + let ty = meta.file_type(); + if ty.is_dir() { + self.handle_devices( + tokio::fs::read_dir(&path).await.with_ctx(|_| { + (ErrorKind::Filesystem, format!("readdir {path:?}")) + })?, + &matches, + ) + .await?; + } else { + let ty = if ty.is_char_device() { + 'c' + } else if ty.is_block_device() { + 'b' + } else { + continue; + }; + let rdev = meta.st_rdev(); + let major = ((rdev >> 8) & 0xfff) as u32; + let minor = ((rdev & 0xff) | ((rdev >> 12) & 0xfff00)) as u32; + self.mknod(&path, ty, major, minor).await?; + } + } + } + Ok(()) + } + .boxed() } pub fn rootfs_dir(&self) -> &Path { @@ -329,7 +426,7 @@ impl LxcContainer { .await?; self.rpc_bind.take().unmount().await?; if let Some(log_mount) = self.log_mount.take() { - log_mount.unmount(true).await?; + log_mount.unmount(false).await?; } self.rootfs.take().unmount(true).await?; let rootfs_path = self.rootfs_dir(); @@ -351,7 +448,10 @@ impl LxcContainer { .invoke(ErrorKind::Lxc) .await?; - self.exited = true; + #[allow(unused_assignments)] + { + self.exited = true; + } Ok(()) } @@ -361,6 +461,17 @@ impl LxcContainer { let sock_path = self.rpc_dir().join(CONTAINER_RPC_SERVER_SOCKET); while tokio::fs::metadata(&sock_path).await.is_err() { if timeout.map_or(false, |t| started.elapsed() > t) { + tracing::error!( + "{:?}", + Command::new("lxc-attach") + .arg(&**self.guid) + .arg("--") + .arg("systemctl") + .arg("status") + .arg("container-runtime") + .invoke(ErrorKind::Unknown) + .await + ); return Err(Error::new( eyre!("timed out waiting for socket"), ErrorKind::Timeout, @@ -371,6 +482,88 @@ impl LxcContainer { tracing::info!("Connected to socket in {:?}", started.elapsed()); Ok(UnixRpcClient::new(sock_path)) } + + pub async fn mknod(&self, path: &Path, ty: char, major: u32, minor: u32) -> Result<(), Error> { + if let Ok(dev_rel) = path.strip_prefix("/dev") { + let parent = dev_rel.parent(); + let media_dev = self.rootfs_dir().join("media/startos/dev"); + let target_path = media_dev.join(dev_rel); + if tokio::fs::metadata(&target_path).await.is_ok() { + return Ok(()); + } + if let Some(parent) = parent { + let p = media_dev.join(parent); + tokio::fs::create_dir_all(&p) + .await + .with_ctx(|_| (ErrorKind::Filesystem, format!("mkdir -p {p:?}")))?; + for p in parent.ancestors() { + Command::new("chown") + .arg("100000:100000") + .arg(media_dev.join(p)) + .invoke(ErrorKind::Filesystem) + .await?; + } + } + Command::new("mknod") + .arg(&target_path) + .arg(&*InternedString::from_display(&ty)) + .arg(&*InternedString::from_display(&major)) + .arg(&*InternedString::from_display(&minor)) + .invoke(ErrorKind::Filesystem) + .await?; + Command::new("chown") + .arg("100000:100000") + .arg(&target_path) + .invoke(ErrorKind::Filesystem) + .await?; + if let Some(parent) = parent { + Command::new("lxc-attach") + .arg(&**self.guid) + .arg("--") + .arg("mkdir") + .arg("-p") + .arg(Path::new("/dev").join(parent)) + .invoke(ErrorKind::Lxc) + .await?; + } + Command::new("lxc-attach") + .arg(&**self.guid) + .arg("--") + .arg("touch") + .arg(&path) + .invoke(ErrorKind::Lxc) + .await?; + Command::new("lxc-attach") + .arg(&**self.guid) + .arg("--") + .arg("mount") + .arg("--bind") + .arg(Path::new("/media/startos/dev").join(dev_rel)) + .arg(&path) + .invoke(ErrorKind::Lxc) + .await?; + } else { + let target_path = self + .rootfs_dir() + .join(path.strip_prefix("/").unwrap_or(&path)); + if tokio::fs::metadata(&target_path).await.is_ok() { + return Ok(()); + } + Command::new("mknod") + .arg(&target_path) + .arg(&*InternedString::from_display(&ty)) + .arg(&*InternedString::from_display(&major)) + .arg(&*InternedString::from_display(&minor)) + .invoke(ErrorKind::Filesystem) + .await?; + Command::new("chown") + .arg("100000:100000") + .arg(&target_path) + .invoke(ErrorKind::Filesystem) + .await?; + } + Ok(()) + } } impl Drop for LxcContainer { fn drop(&mut self) { @@ -414,7 +607,10 @@ impl Drop for LxcContainer { } #[derive(Default, Serialize)] -pub struct LxcConfig {} +pub struct LxcConfig { + pub hardware_acceleration: bool, +} + pub async fn connect(ctx: &RpcContext, container: &LxcContainer) -> Result { use axum::extract::ws::Message; diff --git a/core/src/main/start-cli.rs b/core/src/main/start-cli.rs index d1c5741ab..72b88147c 100644 --- a/core/src/main/start-cli.rs +++ b/core/src/main/start-cli.rs @@ -15,5 +15,8 @@ fn main() { }) { PREFER_DOCKER.set(true).ok(); } - MultiExecutable::default().enable_start_cli().execute() + MultiExecutable::default() + .enable_start_cli() + .set_default("start-cli") + .execute() } diff --git a/core/src/main/start-container.rs b/core/src/main/start-container.rs index 5d812980e..e8f26a343 100644 --- a/core/src/main/start-container.rs +++ b/core/src/main/start-container.rs @@ -3,5 +3,6 @@ use startos::bins::MultiExecutable; fn main() { MultiExecutable::default() .enable_start_container() + .set_default("start-container") .execute() } diff --git a/core/src/net/tls.rs b/core/src/net/tls.rs index 2529a7c86..fde80c093 100644 --- a/core/src/net/tls.rs +++ b/core/src/net/tls.rs @@ -151,103 +151,112 @@ where cx: &mut std::task::Context<'_>, ) -> Poll> { self.in_progress.mutate(|in_progress| { - loop { - if !in_progress.is_empty() { - if let Poll::Ready(Some((handler, res))) = in_progress.poll_next_unpin(cx) { - if let Some(res) = res.transpose() { - self.tls_handler = handler; - return Poll::Ready(res); - } - continue; + // First, check if any in-progress handshakes have completed + if !in_progress.is_empty() { + if let Poll::Ready(Some((handler, res))) = in_progress.poll_next_unpin(cx) { + if let Some(res) = res.transpose() { + self.tls_handler = handler; + return Poll::Ready(res); } + // Connection was rejected (preprocess returned None). + // Yield to the runtime to avoid busy-looping, but wake + // immediately to continue processing. + cx.waker().wake_by_ref(); + return Poll::Pending; } + } - let (metadata, stream) = ready!(self.accept.poll_accept(cx)?); - let mut tls_handler = self.tls_handler.clone(); - let mut fut = async move { - let res = async { - let mut acceptor = LazyConfigAcceptor::new( - Acceptor::default(), - BackTrackingIO::new(stream), - ); - let mut mid: tokio_rustls::StartHandshake> = - match (&mut acceptor).await { - Ok(a) => a, - Err(e) => { - let mut stream = - acceptor.take_io().or_not_found("acceptor io")?; - let (_, buf) = stream.rewind(); - if std::str::from_utf8(buf) - .ok() - .and_then(|buf| { - buf.lines() - .map(|l| l.trim()) - .filter(|l| !l.is_empty()) - .next() - }) - .map_or(false, |buf| { - regex::Regex::new("[A-Z]+ (.+) HTTP/1") - .unwrap() - .is_match(buf) - }) - { - handle_http_on_https(stream).await.log_err(); + // Try to accept a new connection + let (metadata, stream) = ready!(self.accept.poll_accept(cx)?); + let mut tls_handler = self.tls_handler.clone(); + let mut fut = async move { + let res = async { + let mut acceptor = LazyConfigAcceptor::new( + Acceptor::default(), + BackTrackingIO::new(stream), + ); + let mut mid: tokio_rustls::StartHandshake> = + match (&mut acceptor).await { + Ok(a) => a, + Err(e) => { + let mut stream = + acceptor.take_io().or_not_found("acceptor io")?; + let (_, buf) = stream.rewind(); + if std::str::from_utf8(buf) + .ok() + .and_then(|buf| { + buf.lines() + .map(|l| l.trim()) + .filter(|l| !l.is_empty()) + .next() + }) + .map_or(false, |buf| { + regex::Regex::new("[A-Z]+ (.+) HTTP/1") + .unwrap() + .is_match(buf) + }) + { + handle_http_on_https(stream).await.log_err(); - return Ok(None); - } else { - return Err(e).with_kind(ErrorKind::Network); - } + return Ok(None); + } else { + return Err(e).with_kind(ErrorKind::Network); } - }; - let hello = mid.client_hello(); - if let Some(cfg) = tls_handler.get_config(&hello, &metadata).await { - let buffered = mid.io.stop_buffering(); - mid.io - .write_all(&buffered) - .await - .with_kind(ErrorKind::Network)?; - return Ok(match mid.into_stream(Arc::new(cfg)).await { - Ok(stream) => { - let s = stream.get_ref().1; - Some(( - TlsMetadata { - inner: metadata, - tls_info: TlsHandshakeInfo { - sni: s.server_name().map(InternedString::intern), - alpn: s - .alpn_protocol() - .map(|a| MaybeUtf8String(a.to_vec())), - }, + } + }; + let hello = mid.client_hello(); + if let Some(cfg) = tls_handler.get_config(&hello, &metadata).await { + let buffered = mid.io.stop_buffering(); + mid.io + .write_all(&buffered) + .await + .with_kind(ErrorKind::Network)?; + return Ok(match mid.into_stream(Arc::new(cfg)).await { + Ok(stream) => { + let s = stream.get_ref().1; + Some(( + TlsMetadata { + inner: metadata, + tls_info: TlsHandshakeInfo { + sni: s.server_name().map(InternedString::intern), + alpn: s + .alpn_protocol() + .map(|a| MaybeUtf8String(a.to_vec())), }, - Box::pin(stream) as AcceptStream, - )) - } - Err(e) => { - tracing::trace!("Error completing TLS handshake: {e}"); - tracing::trace!("{e:?}"); - None - } - }); - } + }, + Box::pin(stream) as AcceptStream, + )) + } + Err(e) => { + tracing::trace!("Error completing TLS handshake: {e}"); + tracing::trace!("{e:?}"); + None + } + }); + } - Ok(None) - } - .await; - (tls_handler, res) + Ok(None) } - .boxed(); - match fut.poll_unpin(cx) { - Poll::Pending => { - in_progress.push(fut); - return Poll::Pending; + .await; + (tls_handler, res) + } + .boxed(); + match fut.poll_unpin(cx) { + Poll::Pending => { + in_progress.push(fut); + Poll::Pending + } + Poll::Ready((handler, res)) => { + if let Some(res) = res.transpose() { + self.tls_handler = handler; + return Poll::Ready(res); } - Poll::Ready((handler, res)) => { - if let Some(res) = res.transpose() { - self.tls_handler = handler; - return Poll::Ready(res); - } - } - }; + // Connection was rejected (preprocess returned None). + // Yield to the runtime to avoid busy-looping, but wake + // immediately to continue processing. + cx.waker().wake_by_ref(); + Poll::Pending + } } }) } diff --git a/core/src/os_install/mod.rs b/core/src/os_install/mod.rs index 6ff9292b6..649397322 100644 --- a/core/src/os_install/mod.rs +++ b/core/src/os_install/mod.rs @@ -280,8 +280,11 @@ pub async fn execute( 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 overlay = TmpMountGuard::mount( + &OverlayFs::new(vec![lower.path()], &upper, &work), + ReadWrite, + ) + .await?; let boot = MountGuard::mount( &BlockDev::new(&part_info.boot), diff --git a/core/src/registry/asset.rs b/core/src/registry/asset.rs index aa26ded04..03f320c41 100644 --- a/core/src/registry/asset.rs +++ b/core/src/registry/asset.rs @@ -3,7 +3,7 @@ use std::path::Path; use std::sync::Arc; use chrono::{DateTime, Utc}; -use reqwest::Client; +use reqwest::{Client, Response}; use serde::{Deserialize, Serialize}; use tokio::io::AsyncWrite; use ts_rs::TS; @@ -21,14 +21,14 @@ use crate::sign::{AnySignature, AnyVerifyingKey}; use crate::upload::UploadingFile; use crate::util::future::NonDetachingJoinHandle; -#[derive(Debug, Deserialize, Serialize, TS)] +#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct RegistryAsset { #[ts(type = "string")] pub published_at: DateTime, - #[ts(type = "string")] - pub url: Url, + #[ts(type = "string[]")] + pub urls: Vec, pub commitment: Commitment, pub signatures: HashMap, } @@ -42,6 +42,48 @@ impl RegistryAsset { .collect(), ) } + pub async fn load_http_source(&self, client: Client) -> Result { + for url in &self.urls { + if let Ok(source) = HttpSource::new(client.clone(), url.clone()).await { + return Ok(source); + } + } + Err(Error::new( + eyre!("Failed to load any http url"), + ErrorKind::Network, + )) + } + pub async fn load_buffered_http_source( + &self, + client: Client, + progress: PhaseProgressTrackerHandle, + ) -> Result { + for url in &self.urls { + if let Ok(response) = client.get(url.clone()).send().await { + return BufferedHttpSource::from_response(response, progress).await; + } + } + Err(Error::new( + eyre!("Failed to load any http url"), + ErrorKind::Network, + )) + } + pub async fn load_buffered_http_source_with_path( + &self, + path: impl AsRef, + client: Client, + progress: PhaseProgressTrackerHandle, + ) -> Result { + for url in &self.urls { + if let Ok(response) = client.get(url.clone()).send().await { + return BufferedHttpSource::from_response_with_path(path, response, progress).await; + } + } + Err(Error::new( + eyre!("Failed to load any http url"), + ErrorKind::Network, + )) + } } impl RegistryAsset { pub fn validate(&self, context: &str, mut accept: AcceptSigners) -> Result<&Commitment, Error> { @@ -59,7 +101,7 @@ impl Commitment<&'a HttpSource>> RegistryAsset { dst: &mut (impl AsyncWrite + Unpin + Send + ?Sized), ) -> Result<(), Error> { self.commitment - .copy_to(&HttpSource::new(client, self.url.clone()).await?, dst) + .copy_to(&self.load_http_source(client).await?, dst) .await } } @@ -69,7 +111,7 @@ impl RegistryAsset { client: Client, ) -> Result>>, Error> { S9pk::deserialize( - &Arc::new(HttpSource::new(client, self.url.clone()).await?), + &Arc::new(self.load_http_source(client).await?), Some(&self.commitment), ) .await @@ -80,7 +122,7 @@ impl RegistryAsset { progress: PhaseProgressTrackerHandle, ) -> Result>>, Error> { S9pk::deserialize( - &Arc::new(BufferedHttpSource::new(client, self.url.clone(), progress).await?), + &Arc::new(self.load_buffered_http_source(client, progress).await?), Some(&self.commitment), ) .await @@ -98,7 +140,8 @@ impl RegistryAsset { Error, > { let source = Arc::new( - BufferedHttpSource::with_path(path, client, self.url.clone(), progress).await?, + self.load_buffered_http_source_with_path(path, client, progress) + .await?, ); Ok(( S9pk::deserialize(&source, Some(&self.commitment)).await?, @@ -112,26 +155,30 @@ pub struct BufferedHttpSource { file: UploadingFile, } impl BufferedHttpSource { - pub async fn with_path( - path: impl AsRef, - client: Client, - url: Url, - progress: PhaseProgressTrackerHandle, - ) -> Result { - let (mut handle, file) = UploadingFile::with_path(path, progress).await?; - let response = client.get(url).send().await?; - Ok(Self { - _download: tokio::spawn(async move { handle.download(response).await }).into(), - file, - }) - } pub async fn new( client: Client, url: Url, progress: PhaseProgressTrackerHandle, ) -> Result { - let (mut handle, file) = UploadingFile::new(progress).await?; let response = client.get(url).send().await?; + Self::from_response(response, progress).await + } + pub async fn from_response( + response: Response, + progress: PhaseProgressTrackerHandle, + ) -> Result { + let (mut handle, file) = UploadingFile::new(progress).await?; + Ok(Self { + _download: tokio::spawn(async move { handle.download(response).await }).into(), + file, + }) + } + pub async fn from_response_with_path( + path: impl AsRef, + response: Response, + progress: PhaseProgressTrackerHandle, + ) -> Result { + let (mut handle, file) = UploadingFile::with_path(path, progress).await?; Ok(Self { _download: tokio::spawn(async move { handle.download(response).await }).into(), file, diff --git a/core/src/registry/context.rs b/core/src/registry/context.rs index 0375a46d3..dd598b27d 100644 --- a/core/src/registry/context.rs +++ b/core/src/registry/context.rs @@ -7,6 +7,7 @@ use chrono::Utc; use clap::Parser; use cookie::{Cookie, Expiration, SameSite}; use http::HeaderMap; +use imbl::OrdMap; use imbl_value::InternedString; use patch_db::PatchDb; use patch_db::json_ptr::ROOT; @@ -171,6 +172,7 @@ impl CallRemote for CliContext { async fn call_remote( &self, mut method: &str, + _: OrdMap<&'static str, Value>, params: Value, _: Empty, ) -> Result { @@ -240,14 +242,21 @@ impl CallRemote for RpcContext { async fn call_remote( &self, mut method: &str, + metadata: OrdMap<&'static str, Value>, params: Value, RegistryUrlParams { mut registry }: RegistryUrlParams, ) -> Result { let mut headers = HeaderMap::new(); - headers.insert( - DEVICE_INFO_HEADER, - DeviceInfo::load(self).await?.to_header_value(), - ); + let mut device_info = None; + if metadata + .get("get_device_info") + .and_then(|m| m.as_bool()) + .unwrap_or(false) + { + let di = DeviceInfo::load(self).await?; + headers.insert(DEVICE_INFO_HEADER, di.to_header_value()); + device_info = Some(di); + } registry .path_segments_mut() @@ -258,15 +267,21 @@ impl CallRemote for RpcContext { method = method.strip_prefix("registry.").unwrap_or(method); let sig_context = registry.host_str().map(InternedString::from); - crate::middleware::auth::signature::call_remote( + let mut res = crate::middleware::auth::signature::call_remote( self, registry, headers, sig_context.as_deref(), method, - params, + params.clone(), ) - .await + .await?; + + if let Some(device_info) = device_info { + device_info.filter_for_hardware(method, params, &mut res)?; + } + + Ok(res) } } diff --git a/core/src/registry/device_info.rs b/core/src/registry/device_info.rs index 9b76ace68..08f233936 100644 --- a/core/src/registry/device_info.rs +++ b/core/src/registry/device_info.rs @@ -1,5 +1,4 @@ use std::collections::BTreeMap; -use std::convert::identity; use std::ops::Deref; use axum::extract::Request; @@ -7,6 +6,8 @@ use axum::response::Response; use exver::{Version, VersionRange}; use http::HeaderValue; use imbl_value::InternedString; +use patch_db::ModelExt; +use rpc_toolkit::yajrc::RpcMethod; use rpc_toolkit::{Middleware, RpcRequest, RpcResponse}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -15,8 +16,13 @@ use url::Url; use crate::context::RpcContext; use crate::prelude::*; use crate::registry::context::RegistryContext; +use crate::registry::os::index::OsVersionInfoMap; +use crate::registry::package::get::{ + GetPackageParams, GetPackageResponse, GetPackageResponseFull, PackageDetailLevel, +}; +use crate::registry::package::index::PackageVersionInfo; use crate::util::VersionString; -use crate::util::lshw::{LshwDevice, LshwDisplay, LshwProcessor}; +use crate::util::lshw::LshwDevice; use crate::version::VersionT; pub const DEVICE_INFO_HEADER: &str = "X-StartOS-Device-Info"; @@ -25,13 +31,13 @@ pub const DEVICE_INFO_HEADER: &str = "X-StartOS-Device-Info"; #[serde(rename_all = "camelCase")] pub struct DeviceInfo { pub os: OsInfo, - pub hardware: HardwareInfo, + pub hardware: Option, } impl DeviceInfo { pub async fn load(ctx: &RpcContext) -> Result { Ok(Self { os: OsInfo::from(ctx), - hardware: HardwareInfo::load(ctx).await?, + hardware: Some(HardwareInfo::load(ctx).await?), }) } } @@ -41,21 +47,13 @@ impl DeviceInfo { url.query_pairs_mut() .append_pair("os.version", &self.os.version.to_string()) .append_pair("os.compat", &self.os.compat.to_string()) - .append_pair("os.platform", &*self.os.platform) - .append_pair("hardware.arch", &*self.hardware.arch) - .append_pair("hardware.ram", &self.hardware.ram.to_string()); - - for device in &self.hardware.devices { - url.query_pairs_mut().append_pair( - &format!("hardware.device.{}", device.class()), - device.product(), - ); - } + .append_pair("os.platform", &*self.os.platform); HeaderValue::from_str(url.query().unwrap_or_default()).unwrap() } pub fn from_header_value(header: &HeaderValue) -> Result { let query: BTreeMap<_, _> = form_urlencoded::parse(header.as_bytes()).collect(); + let has_hw_info = query.keys().any(|k| k.starts_with("hardware.")); Ok(Self { os: OsInfo { version: query @@ -69,35 +67,120 @@ impl DeviceInfo { .deref() .into(), }, - hardware: HardwareInfo { - arch: query - .get("hardware.arch") - .or_not_found("hardware.arch")? - .parse()?, - ram: query - .get("hardware.ram") - .or_not_found("hardware.ram")? - .parse()?, - devices: identity(query) - .split_off("hardware.device.") - .into_iter() - .filter_map(|(k, v)| match k.strip_prefix("hardware.device.") { - Some("processor") => Some(LshwDevice::Processor(LshwProcessor { - product: v.into_owned(), - })), - Some("display") => Some(LshwDevice::Display(LshwDisplay { - product: v.into_owned(), - })), - Some(class) => { - tracing::warn!("unknown device class: {class}"); - None - } - _ => None, + hardware: has_hw_info + .then(|| { + Ok::<_, Error>(HardwareInfo { + arch: query + .get("hardware.arch") + .or_not_found("hardware.arch")? + .parse()?, + ram: query + .get("hardware.ram") + .or_not_found("hardware.ram")? + .parse()?, + devices: None, }) - .collect(), - }, + }) + .transpose()?, }) } + pub fn filter_for_hardware( + &self, + method: &str, + params: Value, + res: &mut Value, + ) -> Result<(), Error> { + match method { + "package.get" => { + let params: Model = ModelExt::from_value(params); + + let other = params.as_other_versions().de()?; + if params.as_id().transpose_ref().is_some() { + if other.unwrap_or_default() == PackageDetailLevel::Full { + self.filter_package_get_full(ModelExt::value_as_mut(res))?; + } else { + self.filter_package_get(ModelExt::value_as_mut(res))?; + } + } else { + for (_, v) in res.as_object_mut().into_iter().flat_map(|o| o.iter_mut()) { + if other.unwrap_or_default() == PackageDetailLevel::Full { + self.filter_package_get_full(ModelExt::value_as_mut(v))?; + } else { + self.filter_package_get(ModelExt::value_as_mut(v))?; + } + } + } + + Ok(()) + } + "os.version.get" => self.filter_os_version(ModelExt::value_as_mut(res)), + _ => Ok(()), + } + } + + fn filter_package_versions( + &self, + versions: &mut Model>, + ) -> Result<(), Error> { + let alpha_17: Version = "0.4.0-alpha.17".parse()?; + + // Filter package versions using for_device + versions.retain(|_, info| info.for_device(self))?; + + // Alpha.17 compatibility: add legacy fields + if self.os.version <= alpha_17 { + for (_, info) in versions.as_entries_mut()? { + let v = info.as_value_mut(); + if let Some(mut tup) = v["s9pks"].get(0).cloned() { + v["s9pk"] = tup[1].take(); + v["hardwareRequirements"] = tup[0].take(); + v["s9pk"]["url"] = v["s9pk"]["urls"][0].clone(); + } + } + } + + Ok(()) + } + + fn filter_package_get(&self, res: &mut Model) -> Result<(), Error> { + self.filter_package_versions(res.as_best_mut()) + } + + fn filter_package_get_full( + &self, + res: &mut Model, + ) -> Result<(), Error> { + self.filter_package_versions(res.as_best_mut())?; + self.filter_package_versions(res.as_other_versions_mut()) + } + + fn filter_os_version(&self, res: &mut Model) -> Result<(), Error> { + let alpha_17: Version = "0.4.0-alpha.17".parse()?; + + // Filter OS versions based on source_version compatibility + res.retain(|_, info| { + let source_version = info.as_source_version().de()?; + Ok(self.os.version.satisfies(&source_version)) + })?; + + // Alpha.17 compatibility: add url field from urls array + if self.os.version <= alpha_17 { + for (_, info) in res.as_entries_mut()? { + let v = info.as_value_mut(); + for asset_ty in ["iso", "squashfs", "img"] { + for (_, asset) in v[asset_ty] + .as_object_mut() + .into_iter() + .flat_map(|o| o.iter_mut()) + { + asset["url"] = asset["urls"][0].clone(); + } + } + } + } + + Ok(()) + } } #[derive(Clone, Debug, Deserialize, Serialize, TS)] @@ -127,7 +210,7 @@ pub struct HardwareInfo { pub arch: InternedString, #[ts(type = "number")] pub ram: u64, - pub devices: Vec, + pub devices: Option>, } impl HardwareInfo { pub async fn load(ctx: &RpcContext) -> Result { @@ -135,7 +218,7 @@ impl HardwareInfo { Ok(Self { arch: s.as_arch().de()?, ram: s.as_ram().de()?, - devices: s.as_devices().de()?, + devices: Some(s.as_devices().de()?), }) } } @@ -148,11 +231,17 @@ pub struct Metadata { #[derive(Clone)] pub struct DeviceInfoMiddleware { - device_info: Option, + device_info_header: Option, + device_info: Option, + req: Option, } impl DeviceInfoMiddleware { pub fn new() -> Self { - Self { device_info: None } + Self { + device_info_header: None, + device_info: None, + req: None, + } } } @@ -163,7 +252,7 @@ impl Middleware for DeviceInfoMiddleware { _: &RegistryContext, request: &mut Request, ) -> Result<(), Response> { - self.device_info = request.headers_mut().remove(DEVICE_INFO_HEADER); + self.device_info_header = request.headers_mut().remove(DEVICE_INFO_HEADER); Ok(()) } async fn process_rpc_request( @@ -174,9 +263,11 @@ impl Middleware for DeviceInfoMiddleware { ) -> Result<(), RpcResponse> { async move { if metadata.get_device_info { - if let Some(device_info) = &self.device_info { - request.params["__DeviceInfo_device_info"] = - to_value(&DeviceInfo::from_header_value(device_info)?)?; + if let Some(device_info) = &self.device_info_header { + let device_info = DeviceInfo::from_header_value(device_info)?; + request.params["__DeviceInfo_device_info"] = to_value(&device_info)?; + self.device_info = Some(device_info); + self.req = Some(request.clone()); } } @@ -185,4 +276,19 @@ impl Middleware for DeviceInfoMiddleware { .await .map_err(|e| RpcResponse::from_result(Err(e))) } + async fn process_rpc_response( + &mut self, + _: &RegistryContext, + response: &mut RpcResponse, + ) -> () { + if let (Some(req), Some(device_info), Ok(res)) = + (&self.req, &self.device_info, &mut response.result) + { + if let Err(e) = + device_info.filter_for_hardware(req.method.as_str(), req.params.clone(), res) + { + response.result = Err(e).map_err(From::from); + } + } + } } diff --git a/core/src/registry/migrations/m_00_package_signer_scope.rs b/core/src/registry/migrations/m_00_package_signer_scope.rs index d1db9f7b4..a688eb352 100644 --- a/core/src/registry/migrations/m_00_package_signer_scope.rs +++ b/core/src/registry/migrations/m_00_package_signer_scope.rs @@ -5,9 +5,6 @@ use crate::prelude::*; pub struct PackageSignerScopeMigration; impl RegistryMigration for PackageSignerScopeMigration { - fn name(&self) -> &'static str { - "PackageSignerScopeMigration" - } fn action(&self, db: &mut Value) -> Result<(), Error> { for (_, info) in db["index"]["package"]["packages"] .as_object_mut() diff --git a/core/src/registry/migrations/m_01_registry_asset_array.rs b/core/src/registry/migrations/m_01_registry_asset_array.rs new file mode 100644 index 000000000..779a3c86f --- /dev/null +++ b/core/src/registry/migrations/m_01_registry_asset_array.rs @@ -0,0 +1,35 @@ +use imbl::vector; + +use super::RegistryMigration; +use crate::prelude::*; + +pub struct RegistryAssetArray; +impl RegistryMigration for RegistryAssetArray { + fn action(&self, db: &mut Value) -> Result<(), Error> { + for (_, info) in db["index"]["package"]["packages"] + .as_object_mut() + .unwrap() + .iter_mut() + { + for (_, info) in info["versions"].as_object_mut().unwrap().iter_mut() { + let hw_req = info["hardwareRequirements"].take(); + let mut s9pk = info["s9pk"].take(); + s9pk["urls"] = Value::Array(vector![s9pk["url"].take()]); + info["s9pks"] = Value::Array(vector![Value::Array(vector![hw_req, s9pk])]); + } + } + for (_, info) in db["index"]["os"]["versions"] + .as_object_mut() + .unwrap() + .iter_mut() + { + for asset_ty in ["iso", "squashfs", "img"] { + for (_, info) in info[asset_ty].as_object_mut().unwrap().iter_mut() { + info["urls"] = Value::Array(vector![info["url"].take()]); + } + } + } + + Ok(()) + } +} diff --git a/core/src/registry/migrations/mod.rs b/core/src/registry/migrations/mod.rs index 3cb86b783..4cabcdf47 100644 --- a/core/src/registry/migrations/mod.rs +++ b/core/src/registry/migrations/mod.rs @@ -4,22 +4,29 @@ use crate::prelude::*; use crate::registry::RegistryDatabase; mod m_00_package_signer_scope; +mod m_01_registry_asset_array; pub trait RegistryMigration { - fn name(&self) -> &'static str; + fn name(&self) -> &'static str { + let val = std::any::type_name_of_val(self); + val.rsplit_once("::").map_or(val, |v| v.1) + } fn action(&self, db: &mut Value) -> Result<(), Error>; } -pub const MIGRATIONS: &[&dyn RegistryMigration] = - &[&m_00_package_signer_scope::PackageSignerScopeMigration]; +pub const MIGRATIONS: &[&dyn RegistryMigration] = &[ + &m_00_package_signer_scope::PackageSignerScopeMigration, + &m_01_registry_asset_array::RegistryAssetArray, +]; #[instrument(skip_all)] pub fn run_migrations(db: &mut Model) -> Result<(), Error> { let mut migrations = db.as_migrations().de().unwrap_or_default(); for migration in MIGRATIONS { - if !migrations.contains(migration.name()) { + let name = migration.name(); + if !migrations.contains(name) { migration.action(ModelExt::as_value_mut(db))?; - migrations.insert(migration.name().into()); + migrations.insert(name.into()); } } let mut db_deser = db.de()?; diff --git a/core/src/registry/os/asset/add.rs b/core/src/registry/os/asset/add.rs index 915fb882f..0f1d2f061 100644 --- a/core/src/registry/os/asset/add.rs +++ b/core/src/registry/os/asset/add.rs @@ -133,7 +133,7 @@ async fn add_asset( .upsert(&platform, || { Ok(RegistryAsset { published_at: Utc::now(), - url, + urls: vec![url.clone()], commitment: commitment.clone(), signatures: HashMap::new(), }) @@ -146,6 +146,9 @@ async fn add_asset( )) } else { s.signatures.insert(signer, signature); + if !s.urls.contains(&url) { + s.urls.push(url); + } Ok(()) } })?; diff --git a/core/src/registry/os/version/mod.rs b/core/src/registry/os/version/mod.rs index d6b68652f..292be863c 100644 --- a/core/src/registry/os/version/mod.rs +++ b/core/src/registry/os/version/mod.rs @@ -187,7 +187,8 @@ pub async fn get_version( platform, device_info, }: GetOsVersionParams, -) -> Result, Error> { +) -> Result // BTreeMap +{ let source = source.or_else(|| device_info.as_ref().map(|d| d.os.version.clone())); let platform = platform.or_else(|| device_info.as_ref().map(|d| d.os.platform.clone())); if let (Some(pool), Some(server_id), Some(arch)) = (&ctx.pool, server_id, &platform) { @@ -202,33 +203,63 @@ pub async fn get_version( .with_kind(ErrorKind::Database)?; } 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)| { - platform - .as_ref() - .map_or(true, |p| info.squashfs.contains_key(p)) - && version.satisfies(&target) - && source + let mut res = to_value::>( + &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)| { + platform .as_ref() - .map_or(true, |s| s.satisfies(&info.source_version)) - }) - .collect() + .map_or(true, |p| info.squashfs.contains_key(p)) + && version.satisfies(&target) + && source + .as_ref() + .map_or(true, |s| s.satisfies(&info.source_version)) + }) + .collect::>()?, + )?; + + // TODO: remove + if device_info.map_or(false, |d| { + "0.4.0-alpha.17" + .parse::() + .map_or(false, |v| d.os.version <= v) + }) { + for (_, v) in res + .as_object_mut() + .into_iter() + .map(|v| v.iter_mut()) + .flatten() + { + for asset_ty in ["iso", "squashfs", "img"] { + for (_, v) in v[asset_ty] + .as_object_mut() + .into_iter() + .map(|v| v.iter_mut()) + .flatten() + { + v["url"] = v["urls"][0].clone(); + } + } + } + } + Ok(res) } pub fn display_version_info( params: WithIoFormat, - info: BTreeMap, + info: Value, // BTreeMap, ) -> Result<(), Error> { use prettytable::*; + let info = from_value::>(info)?; + if let Some(format) = params.format { return display_serializable(format, info); } diff --git a/core/src/registry/package/add.rs b/core/src/registry/package/add.rs index 342929051..938b5e649 100644 --- a/core/src/registry/package/add.rs +++ b/core/src/registry/package/add.rs @@ -12,12 +12,11 @@ use url::Url; use crate::PackageId; use crate::context::CliContext; use crate::prelude::*; -use crate::progress::{FullProgressTracker, ProgressTrackerWriter, ProgressUnits}; +use crate::progress::FullProgressTracker; use crate::registry::asset::BufferedHttpSource; use crate::registry::context::RegistryContext; use crate::registry::package::index::PackageVersionInfo; use crate::s9pk::S9pk; -use crate::s9pk::merkle_archive::source::ArchiveSource; use crate::s9pk::merkle_archive::source::http::HttpSource; use crate::s9pk::v2::SIG_CONTEXT; use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment; @@ -25,13 +24,14 @@ use crate::sign::ed25519::Ed25519; use crate::sign::{AnySignature, AnyVerifyingKey, SignatureScheme}; use crate::util::VersionString; use crate::util::io::TrackingIO; +use crate::util::serde::Base64; #[derive(Debug, Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct AddPackageParams { - #[ts(type = "string")] - pub url: Url, + #[ts(type = "string[]")] + pub urls: Vec, #[ts(skip)] #[serde(rename = "__Auth_signer")] pub uploader: AnyVerifyingKey, @@ -42,7 +42,7 @@ pub struct AddPackageParams { pub async fn add_package( ctx: RegistryContext, AddPackageParams { - url, + urls, uploader, commitment, signature, @@ -53,17 +53,35 @@ pub async fn add_package( .verify_commitment(&uploader, &commitment, SIG_CONTEXT, &signature)?; let peek = ctx.db.peek().await; let uploader_guid = peek.as_index().as_signers().get_signer(&uploader)?; + + let Some(([url], rest)) = urls.split_at_checked(1) else { + return Err(Error::new( + eyre!("must specify at least 1 url"), + ErrorKind::InvalidRequest, + )); + }; + let s9pk = S9pk::deserialize( &Arc::new(HttpSource::new(ctx.client.clone(), url.clone()).await?), Some(&commitment), ) .await?; + for url in rest { + S9pk::deserialize( + &Arc::new(HttpSource::new(ctx.client.clone(), url.clone()).await?), + Some(&commitment), + ) + .await?; + } + let manifest = s9pk.as_manifest(); - let mut info = PackageVersionInfo::from_s9pk(&s9pk, url).await?; - if !info.s9pk.signatures.contains_key(&uploader) { - info.s9pk.signatures.insert(uploader.clone(), signature); + let mut info = PackageVersionInfo::from_s9pk(&s9pk, urls).await?; + for (_, s9pk) in &mut info.s9pks { + if !s9pk.signatures.contains_key(&uploader) && s9pk.commitment == commitment { + s9pk.signatures.insert(uploader.clone(), signature.clone()); + } } ctx.db @@ -85,7 +103,12 @@ pub async fn add_package( .as_package_mut() .as_packages_mut() .upsert(&manifest.id, || Ok(Default::default()))?; - package.as_versions_mut().insert(&manifest.version, &info)?; + let v = package.as_versions_mut(); + if let Some(prev) = v.as_idx_mut(&manifest.version) { + prev.mutate(|p| p.merge_with(info, true))?; + } else { + v.insert(&manifest.version, &info)?; + } Ok(()) } else { @@ -101,7 +124,10 @@ pub async fn add_package( #[serde(rename_all = "camelCase")] pub struct CliAddPackageParams { pub file: PathBuf, - pub url: Url, + #[arg(long)] + pub url: Vec, + #[arg(long)] + pub no_verify: bool, } pub async fn cli_add_package( @@ -109,7 +135,12 @@ pub async fn cli_add_package( context: ctx, parent_method, method, - params: CliAddPackageParams { file, url }, + params: + CliAddPackageParams { + file, + url, + no_verify, + }, .. }: HandlerArgs, ) -> Result<(), Error> { @@ -117,7 +148,19 @@ pub async fn cli_add_package( let progress = FullProgressTracker::new(); let mut sign_phase = progress.add_phase(InternedString::intern("Signing File"), Some(1)); - let mut verify_phase = progress.add_phase(InternedString::intern("Verifying URL"), Some(100)); + let verify = if !no_verify { + url.iter() + .map(|url| { + let phase = progress.add_phase( + InternedString::from_display(&lazy_format!("Verifying {url}")), + Some(100), + ); + (url.clone(), phase) + }) + .collect() + } else { + Vec::new() + }; let mut index_phase = progress.add_phase( InternedString::intern("Adding File to Registry Index"), Some(1), @@ -131,11 +174,240 @@ pub async fn cli_add_package( let signature = Ed25519.sign_commitment(ctx.developer_key()?, &commitment, SIG_CONTEXT)?; sign_phase.complete(); - verify_phase.start(); - let source = BufferedHttpSource::new(ctx.client.clone(), url.clone(), verify_phase).await?; - let mut src = S9pk::deserialize(&Arc::new(source), Some(&commitment)).await?; - src.serialize(&mut TrackingIO::new(0, &mut tokio::io::sink()), true) - .await?; + for (url, mut phase) in verify { + phase.start(); + let source = BufferedHttpSource::new(ctx.client.clone(), url, phase).await?; + let mut src = S9pk::deserialize(&Arc::new(source), Some(&commitment)).await?; + src.serialize(&mut TrackingIO::new(0, &mut tokio::io::sink()), true) + .await?; + } + + index_phase.start(); + ctx.call_remote::( + &parent_method.into_iter().chain(method).join("."), + imbl_value::json!({ + "urls": &url, + "signature": AnySignature::Ed25519(signature), + "commitment": commitment, + }), + ) + .await?; + index_phase.complete(); + + progress.complete(); + + progress_task.await.with_kind(ErrorKind::Unknown)?; + + Ok(()) +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct RemovePackageParams { + pub id: PackageId, + pub version: VersionString, + #[arg(long)] + pub sighash: Option>, + #[ts(skip)] + #[arg(skip)] + #[serde(rename = "__Auth_signer")] + pub signer: Option, +} + +pub async fn remove_package( + ctx: RegistryContext, + RemovePackageParams { + id, + version, + sighash, + signer, + }: RemovePackageParams, +) -> Result { + let peek = ctx.db.peek().await; + let signer = + signer.ok_or_else(|| Error::new(eyre!("missing signer"), ErrorKind::InvalidRequest))?; + let signer_guid = peek.as_index().as_signers().get_signer(&signer)?; + + let rev = ctx + .db + .mutate(|db| { + if db.as_admins().de()?.contains(&signer_guid) + || db + .as_index() + .as_package() + .as_packages() + .as_idx(&id) + .or_not_found(&id)? + .as_authorized() + .de()? + .get(&signer_guid) + .map_or(false, |v| version.satisfies(v)) + { + if let Some(package) = db + .as_index_mut() + .as_package_mut() + .as_packages_mut() + .as_idx_mut(&id) + { + if let Some(sighash) = sighash { + if if let Some(package) = package.as_versions_mut().as_idx_mut(&version) { + package.as_s9pks_mut().mutate(|s| { + s.retain(|(_, asset)| asset.commitment.root_sighash != sighash); + Ok(s.is_empty()) + })? + } else { + false + } { + package.as_versions_mut().remove(&version)?; + } + } else { + package.as_versions_mut().remove(&version)?; + } + } + Ok(()) + } else { + Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization)) + } + }) + .await; + rev.result.map(|_| rev.revision.is_some()) +} + +#[derive(Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct AddMirrorParams { + #[ts(type = "string")] + pub url: Url, + #[ts(skip)] + #[serde(rename = "__Auth_signer")] + pub uploader: AnyVerifyingKey, + pub commitment: MerkleArchiveCommitment, + pub signature: AnySignature, +} + +pub async fn add_mirror( + ctx: RegistryContext, + AddMirrorParams { + url, + uploader, + commitment, + signature, + }: AddMirrorParams, +) -> Result<(), Error> { + uploader + .scheme() + .verify_commitment(&uploader, &commitment, SIG_CONTEXT, &signature)?; + let peek = ctx.db.peek().await; + let uploader_guid = peek.as_index().as_signers().get_signer(&uploader)?; + + let s9pk = S9pk::deserialize( + &Arc::new(HttpSource::new(ctx.client.clone(), url.clone()).await?), + Some(&commitment), + ) + .await?; + + let manifest = s9pk.as_manifest(); + + let mut info = PackageVersionInfo::from_s9pk(&s9pk, vec![url]).await?; + for (_, s9pk) in &mut info.s9pks { + if !s9pk.signatures.contains_key(&uploader) && s9pk.commitment == commitment { + s9pk.signatures.insert(uploader.clone(), signature.clone()); + } + } + + ctx.db + .mutate(|db| { + if db.as_admins().de()?.contains(&uploader_guid) + || db + .as_index() + .as_package() + .as_packages() + .as_idx(&manifest.id) + .or_not_found(&manifest.id)? + .as_authorized() + .de()? + .get(&uploader_guid) + .map_or(false, |v| manifest.version.satisfies(v)) + { + let package = db + .as_index_mut() + .as_package_mut() + .as_packages_mut() + .as_idx_mut(&manifest.id) + .and_then(|p| p.as_versions_mut().as_idx_mut(&manifest.version)) + .or_not_found(&lazy_format!("{}@{}", &manifest.id, &manifest.version))?; + package.mutate(|p| p.merge_with(info, false))?; + + Ok(()) + } else { + Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization)) + } + }) + .await + .result +} + +#[derive(Debug, Deserialize, Serialize, Parser)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +pub struct CliAddMirrorParams { + pub file: PathBuf, + pub url: Url, + pub no_verify: bool, +} + +pub async fn cli_add_mirror( + HandlerArgs { + context: ctx, + parent_method, + method, + params: + CliAddMirrorParams { + file, + url, + no_verify, + }, + .. + }: HandlerArgs, +) -> Result<(), Error> { + let s9pk = S9pk::open(&file, None).await?; + + let progress = FullProgressTracker::new(); + let mut sign_phase = progress.add_phase(InternedString::intern("Signing File"), Some(1)); + let verify = if !no_verify { + let url = &url; + vec![( + url.clone(), + progress.add_phase( + InternedString::from_display(&lazy_format!("Verifying {url}")), + Some(100), + ), + )] + } else { + Vec::new() + }; + let mut index_phase = progress.add_phase( + InternedString::intern("Adding File to Registry Index"), + Some(1), + ); + + let progress_task = + progress.progress_bar_task(&format!("Adding {} to registry...", file.display())); + + sign_phase.start(); + let commitment = s9pk.as_archive().commitment().await?; + let signature = Ed25519.sign_commitment(ctx.developer_key()?, &commitment, SIG_CONTEXT)?; + sign_phase.complete(); + + for (url, mut phase) in verify { + phase.start(); + let source = BufferedHttpSource::new(ctx.client.clone(), url, phase).await?; + let mut src = S9pk::deserialize(&Arc::new(source), Some(&commitment)).await?; + src.serialize(&mut TrackingIO::new(0, &mut tokio::io::sink()), true) + .await?; + } index_phase.start(); ctx.call_remote::( @@ -159,22 +431,26 @@ pub async fn cli_add_package( #[derive(Debug, Deserialize, Serialize, Parser, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] -pub struct RemovePackageParams { +pub struct RemoveMirrorParams { pub id: PackageId, pub version: VersionString, + #[arg(long)] + #[ts(type = "string")] + pub url: Url, #[ts(skip)] #[arg(skip)] #[serde(rename = "__Auth_signer")] pub signer: Option, } -pub async fn remove_package( +pub async fn remove_mirror( ctx: RegistryContext, - RemovePackageParams { + RemoveMirrorParams { id, version, + url, signer, - }: RemovePackageParams, + }: RemoveMirrorParams, ) -> Result<(), Error> { let peek = ctx.db.peek().await; let signer = @@ -200,8 +476,20 @@ pub async fn remove_package( .as_package_mut() .as_packages_mut() .as_idx_mut(&id) + .and_then(|p| p.as_versions_mut().as_idx_mut(&version)) { - package.as_versions_mut().remove(&version)?; + package.as_s9pks_mut().mutate(|s| { + s.iter_mut() + .for_each(|(_, asset)| asset.urls.retain(|u| u != &url)); + if s.iter().any(|(_, asset)| asset.urls.is_empty()) { + Err(Error::new( + eyre!("cannot remove last mirror from an s9pk"), + ErrorKind::InvalidRequest, + )) + } else { + Ok(()) + } + })?; } Ok(()) } else { diff --git a/core/src/registry/package/get.rs b/core/src/registry/package/get.rs index 775795429..f796622e1 100644 --- a/core/src/registry/package/get.rs +++ b/core/src/registry/package/get.rs @@ -20,12 +20,12 @@ use crate::s9pk::v2::SIG_CONTEXT; use crate::util::VersionString; use crate::util::io::{TrackingIO, to_tmp_path}; use crate::util::serde::{WithIoFormat, display_serializable}; -use crate::util::tui::choose; +use crate::util::tui::{choose, choose_custom_display}; #[derive( Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS, ValueEnum, )] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "kebab-case")] #[ts(export)] pub enum PackageDetailLevel { None, @@ -45,10 +45,11 @@ pub struct PackageInfoShort { pub release_notes: String, } -#[derive(Debug, Deserialize, Serialize, TS, Parser)] +#[derive(Debug, Deserialize, Serialize, TS, Parser, HasModel)] #[serde(rename_all = "camelCase")] #[command(rename_all = "kebab-case")] #[ts(export)] +#[model = "Model"] pub struct GetPackageParams { pub id: Option, #[ts(type = "string | null")] @@ -60,14 +61,14 @@ pub struct GetPackageParams { #[arg(skip)] #[serde(rename = "__DeviceInfo_device_info")] pub device_info: Option, - #[serde(default)] #[arg(default_value = "none")] - pub other_versions: PackageDetailLevel, + pub other_versions: Option, } -#[derive(Debug, Deserialize, Serialize, TS)] +#[derive(Debug, Deserialize, Serialize, TS, HasModel)] #[serde(rename_all = "camelCase")] #[ts(export)] +#[model = "Model"] pub struct GetPackageResponse { #[ts(type = "string[]")] pub categories: BTreeSet, @@ -108,9 +109,10 @@ impl GetPackageResponse { } } -#[derive(Debug, Deserialize, Serialize, TS)] +#[derive(Debug, Deserialize, Serialize, TS, HasModel)] #[serde(rename_all = "camelCase")] #[ts(export)] +#[model = "Model"] pub struct GetPackageResponseFull { #[ts(type = "string[]")] pub categories: BTreeSet, @@ -134,15 +136,15 @@ impl GetPackageResponseFull { pub type GetPackagesResponse = BTreeMap; pub type GetPackagesResponseFull = BTreeMap; -fn get_matching_models<'a>( - db: &'a Model, +fn get_matching_models( + db: &Model, GetPackageParams { id, source_version, device_info, .. }: &GetPackageParams, -) -> Result)>, Error> { +) -> Result)>, Error> { if let Some(id) = id { if let Some(pkg) = db.as_packages().as_idx(id) { vec![(id.clone(), pkg)] @@ -168,11 +170,17 @@ fn get_matching_models<'a>( .unwrap_or(VersionRange::any()), ), ) - })? && device_info - .as_ref() - .map_or(Ok(true), |device_info| info.works_for_device(device_info))? - { - Some((k.clone(), ExtendedVersion::from(v), info)) + })? { + let mut info = info.clone(); + if let Some(device_info) = &device_info { + if info.for_device(device_info)? { + Some((k.clone(), ExtendedVersion::from(v), info)) + } else { + None + } + } else { + Some((k.clone(), ExtendedVersion::from(v), info)) + } } else { None }, @@ -186,12 +194,10 @@ fn get_matching_models<'a>( } pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Result { - use patch_db::ModelExt; - let peek = ctx.db.peek().await; - let mut best: BTreeMap>> = + let mut best: BTreeMap>> = Default::default(); - let mut other: BTreeMap>> = + let mut other: BTreeMap>> = Default::default(); for (id, version, info) in get_matching_models(&peek.as_index().as_package(), ¶ms)? { let package_best = best.entry(id.clone()).or_default(); @@ -217,23 +223,23 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu package_other.insert(version.into(), info); } } - if let Some(id) = params.id { + if let Some(id) = ¶ms.id { let categories = peek .as_index() .as_package() .as_packages() - .as_idx(&id) + .as_idx(id) .map(|p| p.as_categories().de()) .transpose()? .unwrap_or_default(); - let best = best - .remove(&id) + let best: BTreeMap = best + .remove(id) .unwrap_or_default() .into_iter() - .map(|(k, v)| v.de().map(|v| (k, v))) + .map(|(k, i)| Ok::<_, Error>((k, i.de()?))) .try_collect()?; - let other = other.remove(&id).unwrap_or_default(); - match params.other_versions { + let other = other.remove(id).unwrap_or_default(); + match params.other_versions.unwrap_or_default() { PackageDetailLevel::None => to_value(&GetPackageResponse { categories, best, @@ -245,7 +251,7 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu other_versions: Some( other .into_iter() - .map(|(k, v)| from_value(v.as_value().clone()).map(|v| (k, v))) + .map(|(k, i)| from_value(i.into()).map(|v| (k, v))) .try_collect()?, ), }), @@ -254,12 +260,12 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu best, other_versions: other .into_iter() - .map(|(k, v)| v.de().map(|v| (k, v))) + .map(|(k, i)| Ok::<_, Error>((k, i.de()?))) .try_collect()?, }), } } else { - match params.other_versions { + match params.other_versions.unwrap_or_default() { PackageDetailLevel::None => to_value( &best .into_iter() @@ -278,7 +284,7 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu categories, best: best .into_iter() - .map(|(k, v)| v.de().map(|v| (k, v))) + .map(|(k, i)| Ok::<_, Error>((k, i.de()?))) .try_collect()?, other_versions: None, }, @@ -305,14 +311,12 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu categories, best: best .into_iter() - .map(|(k, v)| v.de().map(|v| (k, v))) + .map(|(k, i)| Ok::<_, Error>((k, i.de()?))) .try_collect()?, other_versions: Some( other .into_iter() - .map(|(k, v)| { - from_value(v.as_value().clone()).map(|v| (k, v)) - }) + .map(|(k, i)| from_value(i.into()).map(|v| (k, v))) .try_collect()?, ), }, @@ -339,11 +343,11 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu categories, best: best .into_iter() - .map(|(k, v)| v.de().map(|v| (k, v))) + .map(|(k, i)| Ok::<_, Error>((k, i.de()?))) .try_collect()?, other_versions: other .into_iter() - .map(|(k, v)| v.de().map(|v| (k, v))) + .map(|(k, i)| Ok::<_, Error>((k, i.de()?))) .try_collect()?, }, )) @@ -363,7 +367,7 @@ pub fn display_package_info( } if let Some(_) = params.rest.id { - if params.rest.other_versions == PackageDetailLevel::Full { + if params.rest.other_versions.unwrap_or_default() == PackageDetailLevel::Full { for table in from_value::(info)?.tables() { table.print_tty(false)?; println!(); @@ -375,7 +379,7 @@ pub fn display_package_info( } } } else { - if params.rest.other_versions == PackageDetailLevel::Full { + if params.rest.other_versions.unwrap_or_default() == PackageDetailLevel::Full { for (_, package) in from_value::(info)? { for table in package.tables() { table.print_tty(false)?; @@ -431,7 +435,9 @@ pub async fn cli_download( ) .await?, )?; - let PackageVersionInfo { s9pk, .. } = match res.best.len() { + let PackageVersionInfo { + s9pks: mut s9pk, .. + } = match res.best.len() { 0 => { return Err(Error::new( eyre!( @@ -452,6 +458,75 @@ pub async fn cli_download( res.best.remove(version).unwrap() } }; + let s9pk = match s9pk.len() { + 0 => { + return Err(Error::new( + eyre!( + "Could not find a version of {id} that satisfies {}", + target_version.unwrap_or(VersionRange::Any) + ), + ErrorKind::NotFound, + )); + } + 1 => s9pk.pop().unwrap().1, + _ => { + let (_, asset) = choose_custom_display( + &format!(concat!( + "Multiple packages with different hardware requirements found. ", + "Choose a file to download:" + )), + &s9pk, + |(hw, _)| { + use std::fmt::Write; + let mut res = String::new(); + if let Some(arch) = &hw.arch { + write!( + &mut res, + "{}: {}", + if arch.len() == 1 { + "Architecture" + } else { + "Architectures" + }, + arch.iter().join(", ") + ) + .unwrap(); + } + if !hw.device.is_empty() { + if !res.is_empty() { + write!(&mut res, "; ").unwrap(); + } + write!( + &mut res, + "{}: {}", + if hw.device.len() == 1 { + "Device" + } else { + "Devices" + }, + hw.device.iter().map(|d| &d.description).join(", ") + ) + .unwrap(); + } + if let Some(ram) = hw.ram { + if !res.is_empty() { + write!(&mut res, "; ").unwrap(); + } + write!( + &mut res, + "RAM >={:.2}GiB", + ram as f64 / (1024.0 * 1024.0 * 1024.0) + ) + .unwrap(); + } + + res + }, + ) + .await?; + asset.clone() + } + }; s9pk.validate(SIG_CONTEXT, s9pk.all_signers())?; fetching_progress.complete(); diff --git a/core/src/registry/package/index.rs b/core/src/registry/package/index.rs index 5213ac184..61240f025 100644 --- a/core/src/registry/package/index.rs +++ b/core/src/registry/package/index.rs @@ -1,8 +1,10 @@ use std::collections::{BTreeMap, BTreeSet}; +use std::u32; use chrono::Utc; use exver::{Version, VersionRange}; use imbl_value::InternedString; +use patch_db::ModelExt; use serde::{Deserialize, Serialize}; use ts_rs::TS; use url::Url; @@ -50,7 +52,7 @@ pub struct Category { pub name: String, } -#[derive(Debug, Deserialize, Serialize, HasModel, TS)] +#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[model = "Model"] #[ts(export)] @@ -62,11 +64,10 @@ pub struct DependencyMetadata { pub optional: bool, } -#[derive(Debug, Deserialize, Serialize, HasModel, TS)] +#[derive(Debug, Deserialize, Serialize, HasModel, TS, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[model = "Model"] -#[ts(export)] -pub struct PackageVersionInfo { +pub struct PackageMetadata { #[ts(type = "string")] pub title: InternedString, pub icon: DataUrl<'static>, @@ -93,13 +94,11 @@ pub struct PackageVersionInfo { pub os_version: Version, #[ts(type = "string | null")] pub sdk_version: Option, - pub hardware_requirements: HardwareRequirements, - #[ts(type = "string | null")] - pub source_version: Option, - pub s9pk: RegistryAsset, + #[serde(default)] + pub hardware_acceleration: bool, } -impl PackageVersionInfo { - pub async fn from_s9pk(s9pk: &S9pk, url: Url) -> Result { +impl PackageMetadata { + pub async fn load(s9pk: &S9pk) -> Result { let manifest = s9pk.as_manifest(); let mut dependency_metadata = BTreeMap::new(); for (id, info) in &manifest.dependencies.0 { @@ -131,67 +130,153 @@ impl PackageVersionInfo { dependency_metadata, os_version: manifest.os_version.clone(), sdk_version: manifest.sdk_version.clone(), - hardware_requirements: manifest.hardware_requirements.clone(), - source_version: None, // TODO - s9pk: RegistryAsset { - published_at: Utc::now(), - url, - commitment: s9pk.as_archive().commitment().await?, - signatures: [( - AnyVerifyingKey::Ed25519(s9pk.as_archive().signer()), - AnySignature::Ed25519(s9pk.as_archive().signature().await?), - )] - .into_iter() - .collect(), - }, + hardware_acceleration: manifest.hardware_acceleration.clone(), }) } +} + +#[derive(Debug, Deserialize, Serialize, HasModel, TS)] +#[serde(rename_all = "camelCase")] +#[model = "Model"] +#[ts(export)] +pub struct PackageVersionInfo { + #[serde(flatten)] + pub metadata: PackageMetadata, + #[ts(type = "string | null")] + pub source_version: Option, + pub s9pks: Vec<(HardwareRequirements, RegistryAsset)>, +} +impl PackageVersionInfo { + pub async fn from_s9pk( + s9pk: &S9pk, + urls: Vec, + ) -> Result { + Ok(Self { + metadata: PackageMetadata::load(s9pk).await?, + source_version: None, // TODO + s9pks: vec![( + s9pk.as_manifest().hardware_requirements.clone(), + RegistryAsset { + published_at: Utc::now(), + urls, + commitment: s9pk.as_archive().commitment().await?, + signatures: [( + AnyVerifyingKey::Ed25519(s9pk.as_archive().signer()), + AnySignature::Ed25519(s9pk.as_archive().signature().await?), + )] + .into_iter() + .collect(), + }, + )], + }) + } + pub fn merge_with(&mut self, other: Self, replace_urls: bool) -> Result<(), Error> { + for (hw_req, asset) in other.s9pks { + if let Some((_, matching)) = self + .s9pks + .iter_mut() + .find(|(h, s)| s.commitment == asset.commitment && *h == hw_req) + { + if replace_urls { + matching.urls = asset.urls; + } else { + for url in asset.urls { + if matching.urls.contains(&url) { + continue; + } + matching.urls.push(url); + } + } + } else { + if let Some((h, matching)) = self.s9pks.iter_mut().find(|(h, _)| *h == hw_req) { + *matching = asset; + *h = hw_req; + } else { + self.s9pks.push((hw_req, asset)); + } + } + } + self.s9pks.sort_by_key(|(h, _)| h.specificity_desc()); + Ok(()) + } pub fn table(&self, version: &VersionString) -> prettytable::Table { use prettytable::*; let mut table = Table::new(); - table.add_row(row![bc => &self.title]); + table.add_row(row![bc => &self.metadata.title]); table.add_row(row![br -> "VERSION", AsRef::::as_ref(version)]); - table.add_row(row![br -> "RELEASE NOTES", &self.release_notes]); - table.add_row(row![br -> "ABOUT", &textwrap::wrap(&self.description.short, 80).join("\n")]); + table.add_row(row![br -> "RELEASE NOTES", &self.metadata.release_notes]); + table.add_row( + row![br -> "ABOUT", &textwrap::wrap(&self.metadata.description.short, 80).join("\n")], + ); table.add_row(row![ br -> "DESCRIPTION", - &textwrap::wrap(&self.description.long, 80).join("\n") + &textwrap::wrap(&self.metadata.description.long, 80).join("\n") ]); - table.add_row(row![br -> "GIT HASH", self.git_hash.as_deref().unwrap_or("N/A")]); - table.add_row(row![br -> "LICENSE", &self.license]); - table.add_row(row![br -> "PACKAGE REPO", &self.wrapper_repo.to_string()]); - table.add_row(row![br -> "SERVICE REPO", &self.upstream_repo.to_string()]); - table.add_row(row![br -> "WEBSITE", &self.marketing_site.to_string()]); - table.add_row(row![br -> "SUPPORT", &self.support_site.to_string()]); + table.add_row(row![br -> "GIT HASH", self.metadata.git_hash.as_deref().unwrap_or("N/A")]); + table.add_row(row![br -> "LICENSE", &self.metadata.license]); + table.add_row(row![br -> "PACKAGE REPO", &self.metadata.wrapper_repo.to_string()]); + table.add_row(row![br -> "SERVICE REPO", &self.metadata.upstream_repo.to_string()]); + table.add_row(row![br -> "WEBSITE", &self.metadata.marketing_site.to_string()]); + table.add_row(row![br -> "SUPPORT", &self.metadata.support_site.to_string()]); table } } impl Model { - pub fn works_for_device(&self, device_info: &DeviceInfo) -> Result { - if !self.as_os_version().de()?.satisfies(&device_info.os.compat) { + /// Filters this package version for compatibility with the given device. + /// Returns false if the package is incompatible (should be removed). + /// Modifies s9pks in place to only include compatible variants. + pub fn for_device(&mut self, device_info: &DeviceInfo) -> Result { + if !self + .as_metadata() + .as_os_version() + .de()? + .satisfies(&device_info.os.compat) + { return Ok(false); } - let hw = self.as_hardware_requirements().de()?; - if let Some(arch) = hw.arch { - if !arch.contains(&device_info.hardware.arch) { - return Ok(false); - } - } - if let Some(ram) = hw.ram { - if device_info.hardware.ram < ram { - return Ok(false); - } - } - for device_filter in hw.device { - if !device_info - .hardware - .devices - .iter() - .filter(|d| d.class() == &*device_filter.class) - .any(|d| device_filter.pattern.as_ref().is_match(d.product())) + if let Some(hw) = &device_info.hardware { + self.as_s9pks_mut().mutate(|s9pks| { + s9pks.retain(|(hw_req, _)| { + if let Some(arch) = &hw_req.arch { + if !arch.contains(&hw.arch) { + return false; + } + } + if let Some(ram) = hw_req.ram { + if hw.ram < ram { + return false; + } + } + if let Some(dev) = &hw.devices { + for device_filter in &hw_req.device { + if !dev + .iter() + .filter(|d| d.class() == &*device_filter.class) + .any(|d| device_filter.matches(d)) + { + return false; + } + } + } + true + }); + if hw.devices.is_some() { + s9pks.sort_by_key(|(req, _)| req.specificity_desc()); + } else { + s9pks.sort_by_key(|(req, _)| { + let (dev, arch, ram) = req.specificity_desc(); + (u32::MAX - dev, arch, ram) + }); + } + Ok(()) + })?; + + if ModelExt::as_value(self.as_s9pks()) + .as_array() + .map_or(true, |s| s.is_empty()) { return Ok(false); } diff --git a/core/src/registry/package/mod.rs b/core/src/registry/package/mod.rs index d868c4364..e09dbbb9a 100644 --- a/core/src/registry/package/mod.rs +++ b/core/src/registry/package/mod.rs @@ -32,14 +32,46 @@ pub fn package_api() -> ParentHandler { .no_display() .with_about("Add package to registry index"), ) + .subcommand( + "add-mirror", + from_fn_async(add::add_mirror) + .with_metadata("get_signer", Value::Bool(true)) + .no_cli(), + ) + .subcommand( + "add-mirror", + from_fn_async(add::cli_add_mirror) + .no_display() + .with_about("Add a mirror for an s9pk"), + ) .subcommand( "remove", from_fn_async(add::remove_package) .with_metadata("get_signer", Value::Bool(true)) - .no_display() + .with_custom_display_fn(|args, changed| { + if !changed { + tracing::warn!( + "{}@{}{} does not exist, so not removed", + args.params.id, + args.params.version, + args.params + .sighash + .map_or(String::new(), |h| format!("#{h}")) + ); + } + Ok(()) + }) .with_about("Remove package from registry index") .with_call_remote::(), ) + .subcommand( + "remove-mirror", + from_fn_async(add::remove_mirror) + .with_metadata("get_signer", Value::Bool(true)) + .no_display() + .with_about("Remove a mirror from a package") + .with_call_remote::(), + ) .subcommand( "signer", signer::signer_api::().with_about("Add, remove, and list package signers"), diff --git a/core/src/s9pk/git_hash.rs b/core/src/s9pk/git_hash.rs index b798d0052..e733a08c9 100644 --- a/core/src/s9pk/git_hash.rs +++ b/core/src/s9pk/git_hash.rs @@ -7,7 +7,7 @@ use ts_rs::TS; use crate::prelude::*; use crate::util::Invoke; -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, TS)] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, TS, PartialEq, Eq)] #[ts(type = "string")] pub struct GitHash(String); diff --git a/core/src/s9pk/v2/compat.rs b/core/src/s9pk/v2/compat.rs index 20ff35008..c15baef42 100644 --- a/core/src/s9pk/v2/compat.rs +++ b/core/src/s9pk/v2/compat.rs @@ -242,18 +242,23 @@ impl TryFrom for Manifest { .device .into_iter() .map(|(class, product)| DeviceFilter { - pattern_description: format!( + description: format!( "a {class} device matching the expression {}", product.as_ref() ), class, - pattern: product, + product: Some(product), + ..Default::default() }) .collect(), }, git_hash: value.git_hash, os_version: value.eos_version, sdk_version: None, + hardware_acceleration: match value.main { + PackageProcedure::Docker(d) => d.gpu_acceleration, + PackageProcedure::Script(_) => false, + }, }) } } diff --git a/core/src/s9pk/v2/manifest.rs b/core/src/s9pk/v2/manifest.rs index 68077cdfc..4f18453c4 100644 --- a/core/src/s9pk/v2/manifest.rs +++ b/core/src/s9pk/v2/manifest.rs @@ -15,6 +15,7 @@ use crate::s9pk::git_hash::GitHash; use crate::s9pk::merkle_archive::directory_contents::DirectoryContents; use crate::s9pk::merkle_archive::expected::{Expected, Filter}; use crate::s9pk::v2::pack::ImageConfig; +use crate::util::lshw::{LshwDevice, LshwDisplay, LshwProcessor}; use crate::util::serde::Regex; use crate::util::{VersionString, mime}; use crate::version::{Current, VersionT}; @@ -62,6 +63,8 @@ pub struct Manifest { pub dependencies: Dependencies, #[serde(default)] pub hardware_requirements: HardwareRequirements, + #[serde(default)] + pub hardware_acceleration: bool, pub git_hash: Option, #[serde(default = "current_version")] #[ts(type = "string")] @@ -165,7 +168,7 @@ impl Manifest { } } -#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)] +#[derive(Clone, Debug, Default, Deserialize, Serialize, TS, PartialEq)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct HardwareRequirements { @@ -176,19 +179,122 @@ pub struct HardwareRequirements { #[ts(type = "string[] | null")] pub arch: Option>, } +impl HardwareRequirements { + /// returns a value that can be used as a sort key to get most specific requirements first + pub fn specificity_desc(&self) -> (u32, u32, u64) { + ( + u32::MAX - self.device.len() as u32, // more device requirements = more specific + self.arch.as_ref().map_or(u32::MAX, |a| a.len() as u32), // more arches = less specific + self.ram.map_or(0, |r| r), // more ram = more specific + ) + } +} -#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct DeviceFilter { + pub description: String, #[ts(type = "\"processor\" | \"display\"")] pub class: InternedString, - #[ts(type = "string")] - pub pattern: Regex, - pub pattern_description: String, + #[ts(type = "string | null")] + pub product: Option, + #[ts(type = "string | null")] + pub vendor: Option, + #[ts(optional)] + pub capabilities: Option>, + #[ts(optional)] + pub driver: Option, +} +// Omit description +impl PartialEq for DeviceFilter { + fn eq(&self, other: &Self) -> bool { + self.class == other.class + && self.product == other.product + && self.vendor == other.vendor + && self.capabilities == other.capabilities + && self.driver == other.driver + } +} +impl DeviceFilter { + pub fn matches(&self, device: &LshwDevice) -> bool { + if &*self.class != device.class() { + return false; + } + match device { + LshwDevice::Processor(LshwProcessor { + product, + vendor, + capabilities, + }) => { + if let Some(match_product) = &self.product { + if !product + .as_deref() + .map_or(false, |p| match_product.as_ref().is_match(p)) + { + return false; + } + } + if let Some(match_vendor) = &self.vendor { + if !vendor + .as_deref() + .map_or(false, |v| match_vendor.as_ref().is_match(v)) + { + return false; + } + } + if !self + .capabilities + .as_ref() + .map_or(true, |c| c.is_subset(capabilities)) + { + return false; + } + true + } + LshwDevice::Display(LshwDisplay { + product, + vendor, + capabilities, + driver, + }) => { + if let Some(match_product) = &self.product { + if !product + .as_deref() + .map_or(false, |p| match_product.as_ref().is_match(p)) + { + return false; + } + } + if let Some(match_vendor) = &self.vendor { + if !vendor + .as_deref() + .map_or(false, |v| match_vendor.as_ref().is_match(v)) + { + return false; + } + } + if !self + .capabilities + .as_ref() + .map_or(true, |c| c.is_subset(capabilities)) + { + return false; + } + if !self + .driver + .as_ref() + .map_or(true, |d| Some(d) == driver.as_ref()) + { + return false; + } + true + } + } + } } -#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[derive(Clone, Debug, Deserialize, Serialize, TS, PartialEq, Eq)] #[ts(export)] pub struct Description { pub short: String, @@ -212,7 +318,7 @@ impl Description { } } -#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)] +#[derive(Clone, Debug, Default, Deserialize, Serialize, TS, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct Alerts { diff --git a/core/src/s9pk/v2/pack.rs b/core/src/s9pk/v2/pack.rs index cf42d6e90..9deeab978 100644 --- a/core/src/s9pk/v2/pack.rs +++ b/core/src/s9pk/v2/pack.rs @@ -265,7 +265,7 @@ impl PackParams { } } -#[derive(Debug, Clone, Deserialize, Serialize, TS)] +#[derive(Debug, Default, Clone, Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct ImageConfig { @@ -274,15 +274,8 @@ pub struct ImageConfig { pub arch: BTreeSet, #[ts(type = "string | null")] pub emulate_missing_as: Option, -} -impl Default for ImageConfig { - fn default() -> Self { - Self { - source: ImageSource::Packed, - arch: BTreeSet::new(), - emulate_missing_as: None, - } - } + #[serde(default)] + pub nvidia_container: bool, } #[derive(Parser)] @@ -299,6 +292,8 @@ struct CliImageConfig { arch: Vec, #[arg(long)] emulate_missing_as: Option, + #[arg(long)] + nvidia_container: bool, } impl TryFrom for ImageConfig { type Error = clap::Error; @@ -317,6 +312,7 @@ impl TryFrom for ImageConfig { }, arch: value.arch.into_iter().collect(), emulate_missing_as: value.emulate_missing_as, + nvidia_container: value.nvidia_container, }; res.emulate_missing_as .as_ref() @@ -379,20 +375,21 @@ pub enum ImageSource { DockerTag(String), // Recipe(DirRecipe), } +impl Default for ImageSource { + fn default() -> Self { + ImageSource::Packed + } +} impl ImageSource { pub fn ingredients(&self) -> Vec { match self { Self::Packed => Vec::new(), - Self::DockerBuild { - dockerfile, - workdir, - .. - } => { + Self::DockerBuild { dockerfile, .. } => { vec![ - workdir + dockerfile .as_deref() - .unwrap_or(Path::new(".")) - .join(dockerfile.as_deref().unwrap_or(Path::new("Dockerfile"))), + .unwrap_or(Path::new("Dockerfile")) + .to_owned(), ] } Self::DockerTag(_) => Vec::new(), diff --git a/core/src/service/cli.rs b/core/src/service/cli.rs index 2db07040c..7941124a4 100644 --- a/core/src/service/cli.rs +++ b/core/src/service/cli.rs @@ -2,6 +2,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use clap::Parser; +use imbl::OrdMap; use imbl_value::Value; use once_cell::sync::OnceCell; use rpc_toolkit::yajrc::RpcError; @@ -53,7 +54,13 @@ impl Context for ContainerCliContext { } impl CallRemote for ContainerCliContext { - async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result { + async fn call_remote( + &self, + method: &str, + _: OrdMap<&'static str, Value>, + params: Value, + _: Empty, + ) -> Result { call_remote_socket( tokio::net::UnixStream::connect(&self.0.socket) .await diff --git a/core/src/service/effects/context.rs b/core/src/service/effects/context.rs index b97499332..b1517467a 100644 --- a/core/src/service/effects/context.rs +++ b/core/src/service/effects/context.rs @@ -6,7 +6,7 @@ use crate::prelude::*; use crate::service::Service; #[derive(Clone)] -pub(in crate::service) struct EffectContext(Weak); +pub struct EffectContext(Weak); impl EffectContext { pub fn new(service: Weak) -> Self { Self(service) diff --git a/core/src/service/effects/mod.rs b/core/src/service/effects/mod.rs index 99e67381f..779a427ec 100644 --- a/core/src/service/effects/mod.rs +++ b/core/src/service/effects/mod.rs @@ -15,7 +15,7 @@ mod dependency; mod health; mod net; mod prelude; -mod subcontainer; +pub mod subcontainer; mod system; mod version; diff --git a/core/src/service/effects/subcontainer/mod.rs b/core/src/service/effects/subcontainer/mod.rs index 9cbeae808..24a382291 100644 --- a/core/src/service/effects/subcontainer/mod.rs +++ b/core/src/service/effects/subcontainer/mod.rs @@ -11,6 +11,10 @@ use crate::service::effects::prelude::*; use crate::service::persistent_container::Subcontainer; use crate::util::Invoke; +pub const NVIDIA_OVERLAY_PATH: &str = "/var/tmp/startos/nvidia-overlay"; +pub const NVIDIA_OVERLAY_DEBIAN: &str = "/var/tmp/startos/nvidia-overlay/debian"; +pub const NVIDIA_OVERLAY_GENERIC: &str = "/var/tmp/startos/nvidia-overlay/generic"; + #[cfg(target_os = "linux")] mod sync; @@ -112,8 +116,34 @@ pub async fn create_subcontainer_fs( .with_kind(ErrorKind::Incoherent)?, ); tracing::info!("Mounting overlay {guid} for {image_id}"); + + // Determine which nvidia overlay to use based on distro detection + let nvidia_overlay: &[&str] = if context + .seed + .persistent_container + .s9pk + .as_manifest() + .images + .get(&image_id) + .map_or(false, |i| i.nvidia_container) + { + // Check if image is debian-based by looking for /etc/debian_version + let is_debian = tokio::fs::metadata(image.path().join("etc/debian_version")) + .await + .is_ok(); + if is_debian && tokio::fs::metadata(NVIDIA_OVERLAY_DEBIAN).await.is_ok() { + &[NVIDIA_OVERLAY_DEBIAN] + } else if tokio::fs::metadata(NVIDIA_OVERLAY_GENERIC).await.is_ok() { + &[NVIDIA_OVERLAY_GENERIC] + } else { + &[] + } + } else { + &[] + }; + let subcontainer_wrapper = Subcontainer { - overlay: OverlayGuard::mount(image, &mountpoint).await?, + overlay: OverlayGuard::mount_layers(&[], image, nvidia_overlay, &mountpoint).await?, name: name .unwrap_or_else(|| InternedString::intern(format!("subcontainer-{}", image_id))), image_id: image_id.clone(), diff --git a/core/src/service/mod.rs b/core/src/service/mod.rs index 98af8716e..575d4d619 100644 --- a/core/src/service/mod.rs +++ b/core/src/service/mod.rs @@ -9,7 +9,6 @@ use std::sync::{Arc, Weak}; use std::time::Duration; use axum::extract::ws::Utf8Bytes; -use crate::util::net::WebSocket; use clap::Parser; use futures::future::BoxFuture; use futures::stream::FusedStream; @@ -48,6 +47,7 @@ use crate::util::Never; use crate::util::actor::concurrent::ConcurrentActor; use crate::util::future::NonDetachingJoinHandle; use crate::util::io::{AsyncReadStream, AtomicFile, TermSize, delete_file}; +use crate::util::net::WebSocket; use crate::util::serde::Pem; use crate::util::sync::SyncMutex; use crate::volume::data_dir; diff --git a/core/src/service/persistent_container.rs b/core/src/service/persistent_container.rs index e2faf0a8c..e71b5fd5b 100644 --- a/core/src/service/persistent_container.rs +++ b/core/src/service/persistent_container.rs @@ -96,7 +96,9 @@ impl PersistentContainer { .join("logs") .join(&s9pk.as_manifest().id), ), - LxcConfig::default(), + LxcConfig { + hardware_acceleration: s9pk.manifest.hardware_acceleration, + }, ) .await?; let rpc_client = lxc_container.connect_rpc(Some(RPC_CONNECT_TIMEOUT)).await?; diff --git a/core/src/sign/commitment/merkle_archive.rs b/core/src/sign/commitment/merkle_archive.rs index 111923aef..be17d311f 100644 --- a/core/src/sign/commitment/merkle_archive.rs +++ b/core/src/sign/commitment/merkle_archive.rs @@ -11,7 +11,7 @@ use crate::sign::commitment::{Commitment, Digestable}; use crate::util::io::TrackingIO; use crate::util::serde::Base64; -#[derive(Debug, Deserialize, Serialize, HasModel, TS)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize, HasModel, TS, PartialEq, Eq)] #[serde(rename_all = "camelCase")] #[model = "Model"] #[ts(export)] diff --git a/core/src/status/mod.rs b/core/src/status/mod.rs index 092b618a2..ea4d0da98 100644 --- a/core/src/status/mod.rs +++ b/core/src/status/mod.rs @@ -51,6 +51,7 @@ impl Model { } pub fn stopped(&mut self) -> Result<(), Error> { self.as_started_mut().ser(&None)?; + self.as_health_mut().ser(&Default::default())?; Ok(()) } pub fn restart(&mut self) -> Result<(), Error> { @@ -59,7 +60,7 @@ impl Model { Ok(()) } pub fn init(&mut self) -> Result<(), Error> { - self.as_started_mut().ser(&None)?; + self.stopped()?; self.as_desired_mut().map_mutate(|s| { Ok(match s { DesiredStatus::BackingUp { diff --git a/core/src/tunnel/context.rs b/core/src/tunnel/context.rs index 9d1c22655..6de360bf5 100644 --- a/core/src/tunnel/context.rs +++ b/core/src/tunnel/context.rs @@ -251,6 +251,8 @@ impl CallRemote for CliContext { async fn call_remote( &self, mut method: &str, + + _: OrdMap<&'static str, Value>, params: Value, _: Empty, ) -> Result { @@ -315,6 +317,7 @@ impl CallRemote for RpcContext { async fn call_remote( &self, mut method: &str, + _: OrdMap<&'static str, Value>, params: Value, TunnelUrlParams { tunnel }: TunnelUrlParams, ) -> Result { diff --git a/core/src/util/data_url.rs b/core/src/util/data_url.rs index 20a0060aa..c5c80ae6f 100644 --- a/core/src/util/data_url.rs +++ b/core/src/util/data_url.rs @@ -13,7 +13,7 @@ use ts_rs::TS; use crate::util::mime::{mime, unmime}; use crate::{Error, ErrorKind, ResultExt}; -#[derive(Clone, TS)] +#[derive(Clone, TS, PartialEq, Eq)] #[ts(type = "string")] pub struct DataUrl<'a> { pub mime: InternedString, diff --git a/core/src/util/io.rs b/core/src/util/io.rs index fa6ea9414..fbcd9f840 100644 --- a/core/src/util/io.rs +++ b/core/src/util/io.rs @@ -16,7 +16,7 @@ use clap::builder::ValueParserFactory; use futures::future::{BoxFuture, Fuse}; use futures::{FutureExt, Stream, TryStreamExt}; use inotify::{EventMask, EventStream, Inotify, WatchMask}; -use nix::unistd::{Gid, Uid}; +use nix::unistd::{Gid, Uid, fchown}; use serde::{Deserialize, Serialize}; use tokio::fs::{File, OpenOptions}; use tokio::io::{ @@ -892,6 +892,16 @@ impl TmpDir { Ok(()) } + pub fn leak(mut self) { + std::mem::take(&mut self.path); + } + + pub async fn unmount_and_delete(self) -> Result<(), Error> { + crate::disk::mount::util::unmount_all_under(&self.path, false).await?; + tokio::fs::remove_dir_all(&self.path).await?; + Ok(()) + } + pub async fn gc(self: Arc) -> Result<(), Error> { if let Ok(dir) = Arc::try_unwrap(self) { dir.delete().await @@ -1057,6 +1067,32 @@ pub async fn write_file_atomic( Ok(()) } +#[instrument(skip_all)] +pub async fn write_file_owned_atomic( + path: impl AsRef, + contents: impl AsRef<[u8]>, + uid: u32, + gid: u32, +) -> Result<(), Error> { + let path = path.as_ref(); + if let Some(parent) = path.parent() { + tokio::fs::create_dir_all(parent) + .await + .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("mkdir -p {parent:?}")))?; + } + let mut file = AtomicFile::new(path, None::<&Path>) + .await + .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("create {path:?}")))?; + fchown(&*file, Some(uid.into()), Some(gid.into())).with_kind(ErrorKind::Filesystem)?; + file.write_all(contents.as_ref()) + .await + .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("write {path:?}")))?; + file.save() + .await + .with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("save {path:?}")))?; + Ok(()) +} + fn poll_flush_prefix( mut writer: Pin<&mut W>, cx: &mut std::task::Context<'_>, diff --git a/core/src/util/lshw.rs b/core/src/util/lshw.rs index 98c1a09ab..2f61741b9 100644 --- a/core/src/util/lshw.rs +++ b/core/src/util/lshw.rs @@ -1,9 +1,12 @@ +use std::collections::BTreeSet; + +use imbl_value::InternedString; use serde::{Deserialize, Serialize}; use tokio::process::Command; use ts_rs::TS; +use crate::prelude::*; use crate::util::Invoke; -use crate::{Error, ResultExt}; const KNOWN_CLASSES: &[&str] = &["processor", "display"]; @@ -22,22 +25,57 @@ impl LshwDevice { Self::Display(_) => "display", } } - pub fn product(&self) -> &str { - match self { - Self::Processor(hw) => hw.product.as_str(), - Self::Display(hw) => hw.product.as_str(), + pub fn from_value(value: &Value) -> Option { + match value["class"].as_str() { + Some("processor") => Some(LshwDevice::Processor(LshwProcessor::from_value(value))), + Some("display") => Some(LshwDevice::Display(LshwDisplay::from_value(value))), + _ => None, } } } #[derive(Clone, Debug, Deserialize, Serialize, TS)] pub struct LshwProcessor { - pub product: String, + pub product: Option, + pub vendor: Option, + pub capabilities: BTreeSet, +} +impl LshwProcessor { + fn from_value(value: &Value) -> Self { + Self { + product: value["product"].as_str().map(From::from), + vendor: value["vendor"].as_str().map(From::from), + capabilities: value["capabilities"] + .as_object() + .into_iter() + .flat_map(|o| o.keys()) + .map(|k| k.clone()) + .collect(), + } + } } #[derive(Clone, Debug, Deserialize, Serialize, TS)] pub struct LshwDisplay { - pub product: String, + pub product: Option, + pub vendor: Option, + pub capabilities: BTreeSet, + pub driver: Option, +} +impl LshwDisplay { + fn from_value(value: &Value) -> Self { + Self { + product: value["product"].as_str().map(From::from), + vendor: value["vendor"].as_str().map(From::from), + capabilities: value["capabilities"] + .as_object() + .into_iter() + .flat_map(|o| o.keys()) + .map(|k| k.clone()) + .collect(), + driver: value["configuration"]["driver"].as_str().map(From::from), + } + } } pub async fn lshw() -> Result, Error> { @@ -47,19 +85,10 @@ pub async fn lshw() -> Result, Error> { cmd.arg("-class").arg(*class); } Ok( - serde_json::from_slice::>( - &cmd.invoke(crate::ErrorKind::Lshw).await?, - ) - .with_kind(crate::ErrorKind::Deserialization)? - .into_iter() - .filter_map(|v| match serde_json::from_value(v) { - Ok(a) => Some(a), - Err(e) => { - tracing::error!("Failed to parse lshw output: {e}"); - tracing::debug!("{e:?}"); - None - } - }) - .collect(), + serde_json::from_slice::>(&cmd.invoke(crate::ErrorKind::Lshw).await?) + .with_kind(crate::ErrorKind::Deserialization)? + .iter() + .filter_map(LshwDevice::from_value) + .collect(), ) } diff --git a/core/src/util/serde.rs b/core/src/util/serde.rs index 3bbedb75b..ace93738e 100644 --- a/core/src/util/serde.rs +++ b/core/src/util/serde.rs @@ -1127,6 +1127,11 @@ impl Serialize for Regex { serialize_display(&self.0, serializer) } } +impl PartialEq for Regex { + fn eq(&self, other: &Self) -> bool { + InternedString::from_display(self.as_ref()) == InternedString::from_display(other.as_ref()) + } +} // TODO: make this not allocate #[derive(Debug)] diff --git a/core/src/util/tui.rs b/core/src/util/tui.rs index eb721c2bb..97b399e32 100644 --- a/core/src/util/tui.rs +++ b/core/src/util/tui.rs @@ -95,7 +95,7 @@ pub async fn prompt_multiline< Ok(res) } -pub async fn choose_custom_display<'t, T: std::fmt::Display>( +pub async fn choose_custom_display<'t, T>( prompt: &str, choices: &'t [T], mut display: impl FnMut(&T) -> String, @@ -121,7 +121,7 @@ pub async fn choose_custom_display<'t, T: std::fmt::Display>( if choice.len() < 1 { return Err(Error::new(eyre!("Aborted"), ErrorKind::Cancelled)); } - let (idx, _) = string_choices + let (idx, choice_str) = string_choices .iter() .enumerate() .find(|(_, s)| s.as_str() == choice[0].as_str()) @@ -132,7 +132,7 @@ pub async fn choose_custom_display<'t, T: std::fmt::Display>( ) })?; let choice = &choices[idx]; - println!("{prompt} {choice}"); + println!("{prompt} {choice_str}"); Ok(&choice) } diff --git a/core/src/version/v0_3_6_alpha_8.rs b/core/src/version/v0_3_6_alpha_8.rs index b58d9c9e6..b60045ffa 100644 --- a/core/src/version/v0_3_6_alpha_8.rs +++ b/core/src/version/v0_3_6_alpha_8.rs @@ -1,6 +1,7 @@ use std::path::Path; use exver::{PreReleaseSegment, VersionRange}; +use imbl_value::json; use tokio::fs::File; use super::v0_3_5::V0_3_0_COMPAT; @@ -10,7 +11,7 @@ use crate::context::RpcContext; use crate::install::PKG_ARCHIVE_DIR; use crate::prelude::*; use crate::s9pk::S9pk; -use crate::s9pk::manifest::{DeviceFilter, Manifest}; +use crate::s9pk::manifest::Manifest; use crate::s9pk::merkle_archive::MerkleArchive; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; use crate::s9pk::v2::SIG_CONTEXT; @@ -84,28 +85,8 @@ impl VersionT for Version { let mut manifest = previous_manifest.clone(); - if let Some(device) = - previous_manifest["hardwareRequirements"]["device"].as_object() - { - manifest["hardwareRequirements"]["device"] = to_value( - &device - .into_iter() - .map(|(class, product)| { - Ok::<_, Error>(DeviceFilter { - pattern_description: format!( - "a {class} device matching the expression {}", - &product - ), - class: class.clone(), - pattern: from_value(product.clone())?, - }) - }) - .fold(Ok::<_, Error>(Vec::new()), |acc, value| { - let mut acc = acc?; - acc.push(value?); - Ok(acc) - })?, - )?; + if let Some(_) = previous_manifest["hardwareRequirements"]["device"].as_object() { + manifest["hardwareRequirements"]["device"] = json!([]); } if previous_manifest != manifest { diff --git a/debian/startos/postinst b/debian/startos/postinst index db4a88000..7ed9a5f19 100755 --- a/debian/startos/postinst +++ b/debian/startos/postinst @@ -3,33 +3,39 @@ set -e SYSTEMCTL=systemctl if [ -n "$DPKG_MAINTSCRIPT_PACKAGE" ]; then - SYSTEMCTL=deb-systemd-helper + SYSTEMCTL=deb-systemd-helper fi if [ -f /usr/sbin/grub-probe ] && ! [ -L /usr/sbin/grub-probe ]; then - mv /usr/sbin/grub-probe /usr/sbin/grub-probe-default - ln -s /usr/lib/startos/scripts/grub-probe-eos /usr/sbin/grub-probe + mv /usr/sbin/grub-probe /usr/sbin/grub-probe-default + ln -s /usr/lib/startos/scripts/grub-probe-eos /usr/sbin/grub-probe fi cp /usr/lib/startos/scripts/startos-initramfs-module /etc/initramfs-tools/scripts/startos if ! grep overlay /etc/initramfs-tools/modules > /dev/null; then - echo overlay >> /etc/initramfs-tools/modules + echo overlay >> /etc/initramfs-tools/modules fi update-initramfs -u -k all if [ -f /etc/default/grub ]; then - sed -i '/\(^\|#\)GRUB_CMDLINE_LINUX=/c\GRUB_CMDLINE_LINUX="boot=startos console=ttyS0,115200n8"' /etc/default/grub - sed -i '/\(^\|#\)GRUB_DISTRIBUTOR=/c\GRUB_DISTRIBUTOR="StartOS v$(cat /usr/lib/startos/VERSION.txt)"' /etc/default/grub - sed -i '/\(^\|#\)GRUB_TERMINAL=/c\GRUB_TERMINAL="serial"\nGRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"' /etc/default/grub + sed -i '/\(^\|#\)GRUB_CMDLINE_LINUX=/c\GRUB_CMDLINE_LINUX="boot=startos console=ttyS0,115200n8 console=tty0"' /etc/default/grub + sed -i '/\(^\|#\)GRUB_CMDLINE_LINUX_DEFAULT=/c\GRUB_CMDLINE_LINUX_DEFAULT=""' /etc/default/grub + sed -i '/\(^\|#\)GRUB_DISTRIBUTOR=/c\GRUB_DISTRIBUTOR="StartOS v$(cat /usr/lib/startos/VERSION.txt)"' /etc/default/grub + sed -i '/\(^\|#\)GRUB_TERMINAL=/c\GRUB_TERMINAL="serial"\nGRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"' /etc/default/grub + if grep '^GRUB_SERIAL_COMMAND=' /etc/default/grub > /dev/null; then + sed -i '/\(^\|#\)GRUB_SERIAL_COMMAND=/c\GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"' /etc/default/grub + else + echo 'GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"' >> /etc/default/grub + fi fi VERSION="$(cat /usr/lib/startos/VERSION.txt)" ENVIRONMENT=$(cat /usr/lib/startos/ENVIRONMENT.txt) VERSION_ENV="${VERSION}" if [ -n "${ENVIRONMENT}" ]; then - VERSION_ENV="${VERSION} (${ENVIRONMENT})" + VERSION_ENV="${VERSION} (${ENVIRONMENT})" fi # set /etc/os-release @@ -89,8 +95,8 @@ $SYSTEMCTL mask hibernate.target $SYSTEMCTL mask hybrid-sleep.target if which gsettings > /dev/null; then - gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout '0' - gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-timeout '0' + gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout '0' + gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-timeout '0' fi sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config @@ -122,7 +128,7 @@ ln -sf /usr/lib/startos/scripts/wireguard-vps-proxy-setup /usr/bin/wireguard-vps echo "fs.inotify.max_user_watches=1048576" > /etc/sysctl.d/97-startos.conf if ! getent group | grep '^startos:'; then - groupadd startos + groupadd startos fi rm -f /etc/motd diff --git a/patch-db b/patch-db index bdb5a1011..05c93290c 160000 --- a/patch-db +++ b/patch-db @@ -1 +1 @@ -Subproject commit bdb5a10114a085e86b52ab72e9691ec0db63c4dd +Subproject commit 05c93290c759bdf5e7308a24cf0d4a440ed287a0 diff --git a/sdk/base/lib/osBindings/AddMirrorParams.ts b/sdk/base/lib/osBindings/AddMirrorParams.ts new file mode 100644 index 000000000..2aa708376 --- /dev/null +++ b/sdk/base/lib/osBindings/AddMirrorParams.ts @@ -0,0 +1,9 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AnySignature } from "./AnySignature" +import type { MerkleArchiveCommitment } from "./MerkleArchiveCommitment" + +export type AddMirrorParams = { + url: string + commitment: MerkleArchiveCommitment + signature: AnySignature +} diff --git a/sdk/base/lib/osBindings/AddPackageParams.ts b/sdk/base/lib/osBindings/AddPackageParams.ts index 4395b9b8a..a21619c00 100644 --- a/sdk/base/lib/osBindings/AddPackageParams.ts +++ b/sdk/base/lib/osBindings/AddPackageParams.ts @@ -3,7 +3,7 @@ import type { AnySignature } from "./AnySignature" import type { MerkleArchiveCommitment } from "./MerkleArchiveCommitment" export type AddPackageParams = { - url: string + urls: string[] commitment: MerkleArchiveCommitment signature: AnySignature } diff --git a/sdk/base/lib/osBindings/DeviceFilter.ts b/sdk/base/lib/osBindings/DeviceFilter.ts index 6e6f5810c..c50468e37 100644 --- a/sdk/base/lib/osBindings/DeviceFilter.ts +++ b/sdk/base/lib/osBindings/DeviceFilter.ts @@ -1,7 +1,10 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type DeviceFilter = { + description: string class: "processor" | "display" - pattern: string - patternDescription: string + product: string | null + vendor: string | null + capabilities?: Array + driver?: string } diff --git a/sdk/base/lib/osBindings/GetPackageParams.ts b/sdk/base/lib/osBindings/GetPackageParams.ts index 22046d8e9..ecee3173f 100644 --- a/sdk/base/lib/osBindings/GetPackageParams.ts +++ b/sdk/base/lib/osBindings/GetPackageParams.ts @@ -7,5 +7,5 @@ export type GetPackageParams = { id: PackageId | null targetVersion: string | null sourceVersion: Version | null - otherVersions: PackageDetailLevel + otherVersions: PackageDetailLevel | null } diff --git a/sdk/base/lib/osBindings/ImageConfig.ts b/sdk/base/lib/osBindings/ImageConfig.ts index 2b1033b83..8a53a8450 100644 --- a/sdk/base/lib/osBindings/ImageConfig.ts +++ b/sdk/base/lib/osBindings/ImageConfig.ts @@ -5,4 +5,5 @@ export type ImageConfig = { source: ImageSource arch: string[] emulateMissingAs: string | null + nvidiaContainer: boolean } diff --git a/sdk/base/lib/osBindings/LshwDisplay.ts b/sdk/base/lib/osBindings/LshwDisplay.ts index 25d14b12f..658e4ac85 100644 --- a/sdk/base/lib/osBindings/LshwDisplay.ts +++ b/sdk/base/lib/osBindings/LshwDisplay.ts @@ -1,3 +1,8 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type LshwDisplay = { product: string } +export type LshwDisplay = { + product: string | null + vendor: string | null + capabilities: Array + driver: string | null +} diff --git a/sdk/base/lib/osBindings/LshwProcessor.ts b/sdk/base/lib/osBindings/LshwProcessor.ts index 17a47d477..f0cb4a2c7 100644 --- a/sdk/base/lib/osBindings/LshwProcessor.ts +++ b/sdk/base/lib/osBindings/LshwProcessor.ts @@ -1,3 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type LshwProcessor = { product: string } +export type LshwProcessor = { + product: string | null + vendor: string | null + capabilities: Array +} diff --git a/sdk/base/lib/osBindings/Manifest.ts b/sdk/base/lib/osBindings/Manifest.ts index 8e44e2056..1d0dbfe0a 100644 --- a/sdk/base/lib/osBindings/Manifest.ts +++ b/sdk/base/lib/osBindings/Manifest.ts @@ -31,6 +31,7 @@ export type Manifest = { alerts: Alerts dependencies: Dependencies hardwareRequirements: HardwareRequirements + hardwareAcceleration: boolean gitHash: GitHash | null osVersion: string sdkVersion: string | null diff --git a/sdk/base/lib/osBindings/PackageVersionInfo.ts b/sdk/base/lib/osBindings/PackageVersionInfo.ts index f131dedae..e19cf7d01 100644 --- a/sdk/base/lib/osBindings/PackageVersionInfo.ts +++ b/sdk/base/lib/osBindings/PackageVersionInfo.ts @@ -10,6 +10,8 @@ import type { PackageId } from "./PackageId" import type { RegistryAsset } from "./RegistryAsset" export type PackageVersionInfo = { + sourceVersion: string | null + s9pks: Array<[HardwareRequirements, RegistryAsset]> title: string icon: DataUrl description: Description @@ -26,7 +28,5 @@ export type PackageVersionInfo = { dependencyMetadata: { [key: PackageId]: DependencyMetadata } osVersion: string sdkVersion: string | null - hardwareRequirements: HardwareRequirements - sourceVersion: string | null - s9pk: RegistryAsset + hardwareAcceleration: boolean } diff --git a/sdk/base/lib/osBindings/RegistryAsset.ts b/sdk/base/lib/osBindings/RegistryAsset.ts index 41f09431f..3b8c85bf2 100644 --- a/sdk/base/lib/osBindings/RegistryAsset.ts +++ b/sdk/base/lib/osBindings/RegistryAsset.ts @@ -4,7 +4,7 @@ import type { AnyVerifyingKey } from "./AnyVerifyingKey" export type RegistryAsset = { publishedAt: string - url: string + urls: string[] commitment: Commitment signatures: { [key: AnyVerifyingKey]: AnySignature } } diff --git a/sdk/base/lib/osBindings/RemoveMirrorParams.ts b/sdk/base/lib/osBindings/RemoveMirrorParams.ts new file mode 100644 index 000000000..5d3ef3f6b --- /dev/null +++ b/sdk/base/lib/osBindings/RemoveMirrorParams.ts @@ -0,0 +1,9 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PackageId } from "./PackageId" +import type { Version } from "./Version" + +export type RemoveMirrorParams = { + id: PackageId + version: Version + url: string +} diff --git a/sdk/base/lib/osBindings/RemovePackageParams.ts b/sdk/base/lib/osBindings/RemovePackageParams.ts index aee8d50f2..a22eba4e3 100644 --- a/sdk/base/lib/osBindings/RemovePackageParams.ts +++ b/sdk/base/lib/osBindings/RemovePackageParams.ts @@ -1,5 +1,10 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Base64 } from "./Base64" import type { PackageId } from "./PackageId" import type { Version } from "./Version" -export type RemovePackageParams = { id: PackageId; version: Version } +export type RemovePackageParams = { + id: PackageId + version: Version + sighash: Base64 | null +} diff --git a/sdk/base/lib/osBindings/index.ts b/sdk/base/lib/osBindings/index.ts index f823343eb..f0da142fe 100644 --- a/sdk/base/lib/osBindings/index.ts +++ b/sdk/base/lib/osBindings/index.ts @@ -13,6 +13,7 @@ export { ActionVisibility } from "./ActionVisibility" export { AddAdminParams } from "./AddAdminParams" export { AddAssetParams } from "./AddAssetParams" export { AddCategoryParams } from "./AddCategoryParams" +export { AddMirrorParams } from "./AddMirrorParams" export { AddPackageParams } from "./AddPackageParams" export { AddPackageSignerParams } from "./AddPackageSignerParams" export { AddPackageToCategoryParams } from "./AddPackageToCategoryParams" @@ -171,6 +172,7 @@ export { RegistryInfo } from "./RegistryInfo" export { RemoveAdminParams } from "./RemoveAdminParams" export { RemoveAssetParams } from "./RemoveAssetParams" export { RemoveCategoryParams } from "./RemoveCategoryParams" +export { RemoveMirrorParams } from "./RemoveMirrorParams" export { RemovePackageFromCategoryParams } from "./RemovePackageFromCategoryParams" export { RemovePackageParams } from "./RemovePackageParams" export { RemovePackageSignerParams } from "./RemovePackageSignerParams" diff --git a/sdk/base/lib/types/ManifestTypes.ts b/sdk/base/lib/types/ManifestTypes.ts index 5cc8e6114..fb9c74425 100644 --- a/sdk/base/lib/types/ManifestTypes.ts +++ b/sdk/base/lib/types/ManifestTypes.ts @@ -128,16 +128,17 @@ export type SDKManifest = { /** * @description (optional) A set of hardware requirements for this service. If the user's machine * does not meet these requirements, they will not be able to install this service. - * @property {object[]} devices - TODO Aiden confirm type on the left. List of required devices (displays or processors). + * @property {object[]} devices - List of required devices (display or processor). + * `pattern` refers to a regular expression that at least one device of the specified class must match + * `patternDescription` is what will be displayed to the user about what kind of device is required * @property {number} ram - Minimum RAM requirement (in megabytes MB) * @property {string[]} arch - List of supported arches * @example * ``` - TODO Aiden verify below and provide examples for devices hardwareRequirements: { devices: [ - { class: 'display', value: '' }, - { class: 'processor', value: '' }, + { class: 'display', pattern: 'CometLake', patternDescription: 'A CometLake (10th generation) Intel Integrated GPU' }, + { class: 'processor', pattern: 'i[3579]-10[0-9]{3}U CPU', patternDescription: 'A 10th Generation Intel i-Series processor' }, ], ram: 8192, arch: ['x86-64'], @@ -149,6 +150,11 @@ export type SDKManifest = { readonly ram?: number | null readonly arch?: string[] | null } + + /** + * @description Enable access to hardware acceleration devices (such as /dev/dri, or /dev/nvidia*) + */ + readonly hardwareAcceleration?: boolean } // this is hacky but idk a more elegant way @@ -174,6 +180,7 @@ export type SDKImageInputSpec = { source: Exclude arch?: ArchOptions[A] emulateMissingAs?: ArchOptions[A][number] | null + nvidiaContainer?: boolean } }[keyof ArchOptions] diff --git a/sdk/package/lib/manifest/setupManifest.ts b/sdk/package/lib/manifest/setupManifest.ts index 9b886e16e..2d86bc5cd 100644 --- a/sdk/package/lib/manifest/setupManifest.ts +++ b/sdk/package/lib/manifest/setupManifest.ts @@ -47,6 +47,7 @@ export function buildManifest< v.emulateMissingAs = (v.arch as string[]).includes("aarch64") ? "aarch64" : v.arch[0] || null + v.nvidiaContainer = !!v.nvidiaContainer images[k] = v as ImageConfig return images }, @@ -90,5 +91,6 @@ export function buildManifest< ) : manifest.hardwareRequirements?.arch, }, + hardwareAcceleration: manifest.hardwareAcceleration ?? false, } } diff --git a/web/projects/marketplace/src/pages/show/about.component.ts b/web/projects/marketplace/src/pages/show/about.component.ts index 16f680a59..edbe5c5a5 100644 --- a/web/projects/marketplace/src/pages/show/about.component.ts +++ b/web/projects/marketplace/src/pages/show/about.component.ts @@ -24,7 +24,7 @@ import { MarketplaceItemComponent } from './item.component' icon="" /> - @if (pkg().s9pk?.publishedAt; as published) { + @if (pkg().s9pks[0]?.[1]?.publishedAt; as published) { category === 'all' || p.categories.includes(category!)) .sort((a, b) => { return ( - new Date(b.s9pk.publishedAt).valueOf() - - new Date(a.s9pk.publishedAt).valueOf() + new Date(b.s9pks[0]?.[1].publishedAt!).valueOf() - + new Date(a.s9pks[0]?.[1].publishedAt!).valueOf() ) }) .map(a => ({ ...a })) diff --git a/web/projects/marketplace/src/types.ts b/web/projects/marketplace/src/types.ts index 032d8f546..b8bd85e29 100644 --- a/web/projects/marketplace/src/types.ts +++ b/web/projects/marketplace/src/types.ts @@ -32,10 +32,7 @@ export type StoreData = { packages: MarketplacePkg[] } -export type MarketplacePkgBase = OptionalProperty< - T.PackageVersionInfo, - 's9pk' -> & { +export type MarketplacePkgBase = T.PackageVersionInfo & { id: T.PackageId version: string flavor: string | null diff --git a/web/projects/shared/src/components/markdown.component.ts b/web/projects/shared/src/components/markdown.component.ts index 82c82f5ba..ca260e562 100644 --- a/web/projects/shared/src/components/markdown.component.ts +++ b/web/projects/shared/src/components/markdown.component.ts @@ -1,10 +1,9 @@ -import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { ChangeDetectionStrategy, Component } from '@angular/core' import { toSignal } from '@angular/core/rxjs-interop' -import { ActivatedRoute } from '@angular/router' -import { TuiDialogContext, TuiLoader, TuiNotification } from '@taiga-ui/core' +import { TuiLoader, TuiNotification } from '@taiga-ui/core' import { NgDompurifyPipe } from '@taiga-ui/dompurify' import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus' -import { catchError, ignoreElements, Observable, of } from 'rxjs' +import { catchError, ignoreElements, Observable, of, share } from 'rxjs' import { SafeLinksDirective } from '../directives/safe-links.directive' import { MarkdownPipe } from '../pipes/markdown.pipe' import { getErrorMessage } from '../services/error.service' @@ -34,7 +33,10 @@ import { getErrorMessage } from '../services/error.service' ], }) export class MarkdownComponent { - protected readonly data = injectContext<{ data: Observable }>().data + private readonly data = injectContext<{ + data: Observable + }>().data.pipe(share()) + protected readonly content = toSignal(this.data) protected readonly error = toSignal( this.data.pipe( diff --git a/web/projects/shared/src/directives/docs-link.directive.ts b/web/projects/shared/src/directives/docs-link.directive.ts index e98b76b8c..e0ff60e69 100644 --- a/web/projects/shared/src/directives/docs-link.directive.ts +++ b/web/projects/shared/src/directives/docs-link.directive.ts @@ -5,11 +5,14 @@ import { InjectionToken, input, } from '@angular/core' +import { TuiHintDirective } from '@taiga-ui/core' +import { i18nPipe } from '../i18n/i18n.pipe' export const VERSION = new InjectionToken('VERSION') @Directive({ selector: '[docsLink]', + hostDirectives: [TuiHintDirective], host: { target: '_blank', rel: 'noreferrer', @@ -20,12 +23,18 @@ export class DocsLinkDirective { private readonly version = inject(VERSION) readonly path = input.required() - readonly fragment = input('') protected readonly url = computed(() => { const path = this.path() const relative = path.startsWith('/') ? path : `/${path}` + return `https://docs.start9.com${relative}?os=${this.version}${this.fragment()}` }) + + constructor() { + inject(TuiHintDirective).content.set( + inject(i18nPipe).transform('Documentation'), + ) + } } diff --git a/web/projects/shared/src/i18n/dictionaries/de.ts b/web/projects/shared/src/i18n/dictionaries/de.ts index d33df23a4..c467df91d 100644 --- a/web/projects/shared/src/i18n/dictionaries/de.ts +++ b/web/projects/shared/src/i18n/dictionaries/de.ts @@ -596,4 +596,5 @@ export default { 626: 'Hochladen', 627: 'UI öffnen', 628: 'In Zwischenablage kopiert', + 629: 'Die Liste ist leer', } satisfies i18n diff --git a/web/projects/shared/src/i18n/dictionaries/en.ts b/web/projects/shared/src/i18n/dictionaries/en.ts index e744471c9..0fe5fa9bb 100644 --- a/web/projects/shared/src/i18n/dictionaries/en.ts +++ b/web/projects/shared/src/i18n/dictionaries/en.ts @@ -595,4 +595,5 @@ export const ENGLISH = { 'Upload': 626, // as in, upload a file 'Open UI': 627, // as in, upload a file 'Copied to clipboard': 628, -} as const + 'The list is empty': 629, +} as Record diff --git a/web/projects/shared/src/i18n/dictionaries/es.ts b/web/projects/shared/src/i18n/dictionaries/es.ts index e40a68f18..b1022ff62 100644 --- a/web/projects/shared/src/i18n/dictionaries/es.ts +++ b/web/projects/shared/src/i18n/dictionaries/es.ts @@ -596,4 +596,5 @@ export default { 626: 'Subir', 627: 'Abrir UI', 628: 'Copiado al portapapeles', + 629: 'La lista está vacía', } satisfies i18n diff --git a/web/projects/shared/src/i18n/dictionaries/fr.ts b/web/projects/shared/src/i18n/dictionaries/fr.ts index bad123e1b..aa3cce393 100644 --- a/web/projects/shared/src/i18n/dictionaries/fr.ts +++ b/web/projects/shared/src/i18n/dictionaries/fr.ts @@ -596,4 +596,5 @@ export default { 626: 'Téléverser', 627: 'Ouvrir UI', 628: 'Copié dans le presse-papiers', + 629: 'La liste est vide', } satisfies i18n diff --git a/web/projects/shared/src/i18n/dictionaries/pl.ts b/web/projects/shared/src/i18n/dictionaries/pl.ts index 8683b9caa..e39fdb756 100644 --- a/web/projects/shared/src/i18n/dictionaries/pl.ts +++ b/web/projects/shared/src/i18n/dictionaries/pl.ts @@ -596,4 +596,5 @@ export default { 626: 'Prześlij', 627: 'Otwórz UI', 628: 'Skopiowano do schowka', + 629: 'Lista jest pusta', } satisfies i18n diff --git a/web/projects/ui/src/app/app.providers.ts b/web/projects/ui/src/app/app.providers.ts index 731973051..e2db99464 100644 --- a/web/projects/ui/src/app/app.providers.ts +++ b/web/projects/ui/src/app/app.providers.ts @@ -20,6 +20,7 @@ import { tuiObfuscateOptionsProvider } from '@taiga-ui/cdk' import { TUI_DATE_FORMAT, TUI_DIALOGS_CLOSE, + TUI_MEDIA, tuiAlertOptionsProvider, tuiButtonOptionsProvider, tuiDropdownOptionsProvider, @@ -140,4 +141,12 @@ export const APP_PROVIDERS = [ none: identity, }, }), + { + provide: TUI_MEDIA, + useValue: { + mobile: 1000, + desktopSmall: 1280, + desktopLarge: Infinity, + }, + }, ] diff --git a/web/projects/ui/src/app/routes/portal/components/form.component.ts b/web/projects/ui/src/app/routes/portal/components/form.component.ts index eec7e7f2f..3b300e3c2 100644 --- a/web/projects/ui/src/app/routes/portal/components/form.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/form.component.ts @@ -80,7 +80,7 @@ export interface FormContext { margin: 1rem -1px -1rem; gap: 1rem; background: var(--tui-background-elevation-1); - border-top: 1px solid var(--tui-background-base-alt); + box-shadow: inset 0 1px var(--tui-background-neutral-1); } `, imports: [ diff --git a/web/projects/ui/src/app/routes/portal/components/form/containers/array.component.ts b/web/projects/ui/src/app/routes/portal/components/form/containers/array.component.ts index 8b7a028b1..50a46f7dc 100644 --- a/web/projects/ui/src/app/routes/portal/components/form/containers/array.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/form/containers/array.component.ts @@ -87,6 +87,8 @@ import { FormObjectComponent } from './object.component' } + } @empty { +
{{ 'The list is empty' | i18n }}
} `, styles: ` @@ -99,8 +101,8 @@ import { FormObjectComponent } from './object.component' .label { display: flex; - font-size: 1.25rem; - font-weight: bold; + align-items: center; + font: var(--tui-font-heading-6); } .add { @@ -157,6 +159,17 @@ import { FormObjectComponent } from './object.component' animation-name: tuiFade, tuiCollapse; } } + + .placeholder { + display: flex; + align-items: center; + justify-content: center; + height: var(--tui-height-m); + color: var(--tui-text-tertiary); + border-radius: var(--tui-radius-m); + border: 1px dashed var(--tui-background-neutral-1); + margin: 0.5rem 0 0; + } `, hostDirectives: [ControlDirective], imports: [ diff --git a/web/projects/ui/src/app/routes/portal/components/form/controls/multiselect.component.ts b/web/projects/ui/src/app/routes/portal/components/form/controls/multiselect.component.ts index 91995576f..731bbe594 100644 --- a/web/projects/ui/src/app/routes/portal/components/form/controls/multiselect.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/form/controls/multiselect.component.ts @@ -1,12 +1,11 @@ -import { Component } from '@angular/core' +import { Component, computed } from '@angular/core' import { FormsModule } from '@angular/forms' import { invert } from '@start9labs/shared' import { IST } from '@start9labs/start-sdk' -import { tuiPure } from '@taiga-ui/cdk' import { TuiIcon, TuiTextfield } from '@taiga-ui/core' import { TuiChevron, TuiMultiSelect, TuiTooltip } from '@taiga-ui/kit' -import { Control } from './control' import { HintPipe } from '../pipes/hint.pipe' +import { Control } from './control' @Component({ selector: 'form-multiselect', @@ -21,7 +20,8 @@ import { HintPipe } from '../pipes/hint.pipe' [disabled]="disabled" [readOnly]="readOnly" [items]="items" - [(ngModel)]="selected" + [ngModel]="selected()" + (ngModelChange)="onSelected($event)" (blur)="control.onTouched()" > @if (spec | hint; as hint) { @@ -58,30 +58,26 @@ export class FormMultiselectComponent extends Control< private readonly isExceedingLimit = (item: string) => !!this.spec.maxLength && - this.selected.length >= this.spec.maxLength && - !this.selected.includes(item) + this.selected().length >= this.spec.maxLength && + !this.selected().includes(item) readonly disabledItemHandler = (item: string): boolean => this.isDisabled(item) || this.isExceedingLimit(item) readonly items = Object.values(this.spec.values) + readonly selected = computed( + () => + this.control.value().map((key: string) => this.spec.values[key] || '') || + [], + ) get disabled(): boolean { return typeof this.spec.disabled === 'string' } - get selected(): string[] { - return this.memoize(this.value) - } - - set selected(value: string[]) { + onSelected(value: string[]) { this.value = Object.entries(this.spec.values) .filter(([_, v]) => value.includes(v)) .map(([k]) => k) } - - @tuiPure - private memoize(value: null | readonly string[]): string[] { - return value?.map(key => this.spec.values[key] || '') || [] - } } diff --git a/web/projects/ui/src/app/routes/portal/routes/marketplace/marketplace.component.ts b/web/projects/ui/src/app/routes/portal/routes/marketplace/marketplace.component.ts index 82c6e2d08..8c96721cd 100644 --- a/web/projects/ui/src/app/routes/portal/routes/marketplace/marketplace.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/marketplace/marketplace.component.ts @@ -111,11 +111,11 @@ import { ConfigService } from 'src/app/services/config.service' &-list { display: grid; grid-template-columns: repeat(1, minmax(0, 1fr)); - gap: 4rem 3rem; + gap: 3.5rem 2.5rem; padding: 1.5rem; @media (min-width: 768px) { - padding: 2rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); } @media (min-width: 1024px) { grid-template-columns: repeat(2, minmax(0, 1fr)); diff --git a/web/projects/ui/src/app/routes/portal/routes/services/routes/about.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/routes/about.component.ts index 6f59bcc14..3d70470c1 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/routes/about.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/routes/about.component.ts @@ -10,7 +10,7 @@ import { } from '@start9labs/shared' import { TuiCell } from '@taiga-ui/layout' import { PatchDB } from 'patch-db-client' -import { from, map } from 'rxjs' +import { defer, map } from 'rxjs' import { ApiService } from 'src/app/services/api/embassy-api.service' import { DataModel } from 'src/app/services/patch-db/data-model' import { getManifest } from 'src/app/utils/get-package-data' @@ -54,14 +54,13 @@ import { }) export default class ServiceAboutRoute { private readonly pkgId = getPkgId() + private readonly api = inject(ApiService) private readonly copyService = inject(CopyService) private readonly markdown = inject(DialogService).openComponent(MARKDOWN, { label: 'License', size: 'l', - data: from( - inject(ApiService).getStatic( - `/s9pk/installed/${this.pkgId}.s9pk/LICENSE.md`, - ), + data: defer(() => + this.api.getStatic([`/s9pk/installed/${this.pkgId}.s9pk/LICENSE.md`], {}), ), }) diff --git a/web/projects/ui/src/app/routes/portal/routes/sideload/sideload.utils.ts b/web/projects/ui/src/app/routes/portal/routes/sideload/sideload.utils.ts index 34895e55a..d960c4d0d 100644 --- a/web/projects/ui/src/app/routes/portal/routes/sideload/sideload.utils.ts +++ b/web/projects/ui/src/app/routes/portal/routes/sideload/sideload.utils.ts @@ -41,6 +41,7 @@ async function parseS9pk(file: File): Promise { sourceVersion: s9pk.manifest.canMigrateFrom, flavor: ExtendedVersion.parse(s9pk.manifest.version).flavor, fullLicense: await s9pk.license(), + s9pks: [], } } diff --git a/web/projects/ui/src/app/routes/portal/routes/updates/item.component.ts b/web/projects/ui/src/app/routes/portal/routes/updates/item.component.ts index 42d28e88f..c8348945a 100644 --- a/web/projects/ui/src/app/routes/portal/routes/updates/item.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/updates/item.component.ts @@ -70,7 +70,7 @@ import UpdatesComponent from './updates.component' {{ item().gitHash }} - {{ item().s9pk.publishedAt | date }} + {{ item().s9pks[0]?.[1]?.publishedAt | date }}