Bugfixes for alpha.12 (#3049)

* squashfs-wip

* sdk fixes

* misc fixes

* bump sdk

* Include StartTunnel installation command

Added installation instructions for StartTunnel.

* CA instead of leaf for StartTunnel (#3046)

* updated docs for CA instead of cert

* generate ca instead of self-signed in start-tunnel

* Fix formatting in START-TUNNEL.md installation instructions

* Fix formatting in START-TUNNEL.md

* fix infinite loop

* add success message to install

* hide loopback and bridge gateways

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>

* prevent gateways from getting stuck empty

* fix set-password

* misc networking fixes

* build and efi fixes

* efi fixes

* alpha.13

* remove cross

* fix tests

* provide path to upgrade

* fix networkmanager issues

* remove squashfs before creating

---------

Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
This commit is contained in:
Aiden McClelland
2025-11-15 22:33:03 -07:00
committed by GitHub
parent edb916338c
commit 2fbaaebf44
61 changed files with 856 additions and 693 deletions

View File

@@ -102,12 +102,6 @@ jobs:
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || ''); core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- name: Use Beta Toolchain
run: rustup default beta
- name: Setup Cross
run: cargo install cross --git https://github.com/cross-rs/cross
- name: Make - name: Make
run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar
env: env:

View File

@@ -27,11 +27,5 @@ jobs:
with: with:
node-version: ${{ env.NODEJS_VERSION }} node-version: ${{ env.NODEJS_VERSION }}
- name: Use Beta Toolchain
run: rustup default beta
- name: Setup Cross
run: cargo install cross --git https://github.com/cross-rs/cross
- name: Build And Run Tests - name: Build And Run Tests
run: make test run: make test

View File

@@ -160,7 +160,7 @@ results/$(REGISTRY_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-reg
tunnel-deb: results/$(TUNNEL_BASENAME).deb tunnel-deb: results/$(TUNNEL_BASENAME).deb
results/$(TUNNEL_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-tunnel) $(TUNNEL_TARGETS) results/$(TUNNEL_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-tunnel) $(TUNNEL_TARGETS)
PROJECT=start-tunnel PLATFORM=$(ARCH) REQUIRES=debian DEPENDS=wireguard-tools,iptables ./build/os-compat/run-compat.sh ./dpkg-build.sh PROJECT=start-tunnel PLATFORM=$(ARCH) REQUIRES=debian DEPENDS=wireguard-tools,iptables,conntrack ./build/os-compat/run-compat.sh ./dpkg-build.sh
$(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE) $(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE)
@@ -226,7 +226,7 @@ wormhole-squashfs: results/$(BASENAME).squashfs
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}')) $(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
@echo "Paste the following command into the shell of your StartOS server:" @echo "Paste the following command into the shell of your StartOS server:"
@echo @echo
@wormhole send results/$(BASENAME).squashfs 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo sh -c '"'"'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE) && /usr/lib/startos/scripts/prune-boot && cd /media/startos/images && wormhole receive --accept-file %s && CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/use-img ./$(BASENAME).squashfs'"'"'\n", $$3 }' @wormhole send results/$(BASENAME).squashfs 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo sh -c '"'"'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE) && /usr/lib/startos/scripts/prune-boot && cd /media/startos/images && wormhole receive --accept-file %s && CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/upgrade ./$(BASENAME).squashfs'"'"'\n", $$3 }'
update: $(STARTOS_TARGETS) update: $(STARTOS_TARGETS)
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
@@ -254,7 +254,7 @@ update-squashfs: results/$(BASENAME).squashfs
$(call ssh,'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE)') $(call ssh,'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE)')
$(call ssh,'/usr/lib/startos/scripts/prune-boot') $(call ssh,'/usr/lib/startos/scripts/prune-boot')
$(call cp,results/$(BASENAME).squashfs,/media/startos/images/next.rootfs) $(call cp,results/$(BASENAME).squashfs,/media/startos/images/next.rootfs)
$(call ssh,'sudo CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/use-img /media/startos/images/next.rootfs') $(call ssh,'sudo CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/upgrade /media/startos/images/next.rootfs')
emulate-reflash: $(STARTOS_TARGETS) emulate-reflash: $(STARTOS_TARGETS)
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi

View File

@@ -14,6 +14,14 @@ Use it for private remote access to self-hosted services running on a personal s
- **Forward Ports**: Forwarding a port creates a "reverse tunnel", exposing a specific port on a specific device to the public Internet. - **Forward Ports**: Forwarding a port creates a "reverse tunnel", exposing a specific port on a specific device to the public Internet.
## Features
- **Create Subnets**: Each subnet creates a private, virtual local area network (VLAN), similar to the LAN created by a home router.
- **Add Devices**: When you add a device (server, phone, laptop) to a subnet, it receives a LAN IP address on that subnet as well as a unique Wireguard config that must be copied, downloaded, or scanned into the device.
- **Forward Ports**: Forwarding a port creates a "reverse tunnel", exposing a specific port on a specific device to the public Internet.
## Installation ## Installation
1. Rent a low cost VPS. For most use cases, the cheapest option should be enough. 1. Rent a low cost VPS. For most use cases, the cheapest option should be enough.

View File

@@ -7,6 +7,7 @@ bmon
btrfs-progs btrfs-progs
ca-certificates ca-certificates
cifs-utils cifs-utils
conntrack
cryptsetup cryptsetup
curl curl
dmidecode dmidecode
@@ -19,7 +20,6 @@ flashrom
fuse3 fuse3
grub-common grub-common
grub-efi grub-efi
grub2-common
htop htop
httpdirfs httpdirfs
iotop iotop

View File

@@ -1,6 +1,5 @@
- grub-common - grub-common
- grub-efi - grub-efi
- grub2-common
+ parted + parted
+ raspberrypi-net-mods + raspberrypi-net-mods
+ raspberrypi-sys-mods + raspberrypi-sys-mods

View File

@@ -10,24 +10,24 @@ fi
POSITIONAL_ARGS=() POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
--no-sync) --no-sync)
NO_SYNC=1 NO_SYNC=1
shift shift
;; ;;
--create) --create)
ONLY_CREATE=1 ONLY_CREATE=1
shift shift
;; ;;
-*|--*) -*|--*)
echo "Unknown option $1" echo "Unknown option $1"
exit 1 exit 1
;; ;;
*) *)
POSITIONAL_ARGS+=("$1") # save positional arg POSITIONAL_ARGS+=("$1") # save positional arg
shift # past argument shift # past argument
;; ;;
esac esac
done done
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
@@ -35,7 +35,7 @@ set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
if [ -z "$NO_SYNC" ]; then if [ -z "$NO_SYNC" ]; then
echo 'Syncing...' echo 'Syncing...'
umount -R /media/startos/next 2> /dev/null umount -R /media/startos/next 2> /dev/null
umount -R /media/startos/upper 2> /dev/null umount /media/startos/upper 2> /dev/null
rm -rf /media/startos/upper /media/startos/next rm -rf /media/startos/upper /media/startos/next
mkdir /media/startos/upper mkdir /media/startos/upper
mount -t tmpfs tmpfs /media/startos/upper mount -t tmpfs tmpfs /media/startos/upper
@@ -43,8 +43,6 @@ if [ -z "$NO_SYNC" ]; then
mount -t overlay \ mount -t overlay \
-olowerdir=/media/startos/current,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \ -olowerdir=/media/startos/current,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \
overlay /media/startos/next overlay /media/startos/next
mkdir -p /media/startos/next/media/startos/root
mount --bind /media/startos/root /media/startos/next/media/startos/root
fi fi
if [ -n "$ONLY_CREATE" ]; then if [ -n "$ONLY_CREATE" ]; then
@@ -56,12 +54,18 @@ mkdir -p /media/startos/next/dev
mkdir -p /media/startos/next/sys mkdir -p /media/startos/next/sys
mkdir -p /media/startos/next/proc mkdir -p /media/startos/next/proc
mkdir -p /media/startos/next/boot mkdir -p /media/startos/next/boot
mkdir -p /media/startos/next/media/startos/root
mount --bind /run /media/startos/next/run mount --bind /run /media/startos/next/run
mount --bind /tmp /media/startos/next/tmp mount --bind /tmp /media/startos/next/tmp
mount --bind /dev /media/startos/next/dev mount --bind /dev /media/startos/next/dev
mount --bind /sys /media/startos/next/sys mount --bind /sys /media/startos/next/sys
mount --bind /proc /media/startos/next/proc mount --bind /proc /media/startos/next/proc
mount --bind /boot /media/startos/next/boot mount --bind /boot /media/startos/next/boot
mount --bind /media/startos/root /media/startos/next/media/startos/root
if mountpoint /sys/firmware/efi/efivars 2> /dev/null; then
mount --bind /sys/firmware/efi/efivars /media/startos/next/sys/firmware/efi/efivars
fi
if [ -z "$*" ]; then if [ -z "$*" ]; then
chroot /media/startos/next chroot /media/startos/next
@@ -71,6 +75,10 @@ else
CHROOT_RES=$? CHROOT_RES=$?
fi fi
if mountpoint /media/startos/next/sys/firmware/efi/efivars 2> /dev/null; then
umount /media/startos/next/sys/firmware/efi/efivars
fi
umount /media/startos/next/run umount /media/startos/next/run
umount /media/startos/next/tmp umount /media/startos/next/tmp
umount /media/startos/next/dev umount /media/startos/next/dev
@@ -87,11 +95,12 @@ if [ "$CHROOT_RES" -eq 0 ]; then
echo 'Upgrading...' echo 'Upgrading...'
rm -f /media/startos/images/next.squashfs
if ! time mksquashfs /media/startos/next /media/startos/images/next.squashfs -b 4096 -comp gzip; then if ! time mksquashfs /media/startos/next /media/startos/images/next.squashfs -b 4096 -comp gzip; then
umount -R /media/startos/next umount -l /media/startos/next
umount -R /media/startos/upper umount -l /media/startos/upper
rm -rf /media/startos/upper /media/startos/next rm -rf /media/startos/upper /media/startos/next
exit 1 exit 1
fi fi
hash=$(b3sum /media/startos/images/next.squashfs | head -c 32) hash=$(b3sum /media/startos/images/next.squashfs | head -c 32)
mv /media/startos/images/next.squashfs /media/startos/images/${hash}.rootfs mv /media/startos/images/next.squashfs /media/startos/images/${hash}.rootfs
@@ -103,5 +112,5 @@ if [ "$CHROOT_RES" -eq 0 ]; then
fi fi
umount -R /media/startos/next umount -R /media/startos/next
umount -R /media/startos/upper umount /media/startos/upper
rm -rf /media/startos/upper /media/startos/next rm -rf /media/startos/upper /media/startos/next

View File

@@ -5,34 +5,25 @@ if [ -z "$sip" ] || [ -z "$dip" ] || [ -z "$sport" ] || [ -z "$dport" ]; then
exit 1 exit 1
fi fi
# Helper function to check if a rule exists rule_exists() {
nat_rule_exists() {
iptables -t nat -C "$@" 2>/dev/null iptables -t nat -C "$@" 2>/dev/null
} }
# Helper function to add or delete a rule idempotently apply_rule() {
# Usage: apply_rule [add|del] <iptables args...> if [ "$UNDO" = "1" ]; then
apply_nat_rule() {
local action="$1"
shift
if [ "$action" = "add" ]; then
# Only add if rule doesn't exist
if ! rule_exists "$@"; then
iptables -t nat -A "$@"
fi
elif [ "$action" = "del" ]; then
if rule_exists "$@"; then if rule_exists "$@"; then
iptables -t nat -D "$@" iptables -t nat -D "$@"
fi fi
else
if ! rule_exists "$@"; then
iptables -t nat -A "$@"
fi
fi fi
} }
if [ "$UNDO" = 1 ]; then apply_rule PREROUTING -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport
action="del" apply_rule OUTPUT -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport
else
action="add"
fi
apply_nat_rule "$action" PREROUTING -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport if [ "$UNDO" = 1 ]; then
apply_nat_rule "$action" OUTPUT -p tcp -d $sip --dport $sport -j DNAT --to-destination $dip:$dport conntrack -D -p tcp -d $sip --dport $sport
fi

82
build/lib/scripts/upgrade Executable file
View File

@@ -0,0 +1,82 @@
#!/bin/bash
set -e
SOURCE_DIR="$(dirname $(realpath "${BASH_SOURCE[0]}"))"
if [ "$UID" -ne 0 ]; then
>&2 echo 'Must be run as root'
exit 1
fi
if ! [ -f "$1" ]; then
>&2 echo "usage: $0 <SQUASHFS>"
exit 1
fi
echo 'Upgrading...'
hash=$(b3sum $1 | head -c 32)
if [ -n "$2" ] && [ "$hash" != "$CHECKSUM" ]; then
>&2 echo 'Checksum mismatch'
exit 2
fi
unsquashfs -f -d / $1 boot
umount -R /media/startos/next 2> /dev/null || true
umount /media/startos/upper 2> /dev/null || true
umount /media/startos/lower 2> /dev/null || true
mkdir -p /media/startos/upper
mount -t tmpfs tmpfs /media/startos/upper
mkdir -p /media/startos/lower /media/startos/upper/data /media/startos/upper/work /media/startos/next
mount $1 /media/startos/lower
mount -t overlay \
-olowerdir=/media/startos/lower,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \
overlay /media/startos/next
mkdir -p /media/startos/next/run
mkdir -p /media/startos/next/dev
mkdir -p /media/startos/next/sys
mkdir -p /media/startos/next/proc
mkdir -p /media/startos/next/boot
mkdir -p /media/startos/next/media/startos/root
mount --bind /run /media/startos/next/run
mount --bind /tmp /media/startos/next/tmp
mount --bind /dev /media/startos/next/dev
mount --bind /sys /media/startos/next/sys
mount --bind /proc /media/startos/next/proc
mount --bind /boot /media/startos/next/boot
mount --bind /media/startos/root /media/startos/next/media/startos/root
if mountpoint /boot/efi 2> /dev/null; then
mkdir -p /media/startos/next/boot/efi
mount --bind /boot/efi /media/startos/next/boot/efi
fi
if mountpoint /sys/firmware/efi/efivars 2> /dev/null; then
mount --bind /sys/firmware/efi/efivars /media/startos/next/sys/firmware/efi/efivars
fi
chroot /media/startos/next bash -e << "EOF"
if dpkg -s grub-common 2>&1 > /dev/null; then
grub-install /dev/$(eval $(lsblk -o MOUNTPOINT,PKNAME -P | grep 'MOUNTPOINT="/media/startos/root"') && echo $PKNAME)
update-grub
fi
EOF
sync
umount -R /media/startos/next
umount /media/startos/upper
umount /media/startos/lower
mv $1 /media/startos/images/${hash}.rootfs
ln -rsf /media/startos/images/${hash}.rootfs /media/startos/config/current.rootfs
sync
echo 'System upgrade complete. Reboot to apply changes...'

View File

@@ -1,61 +0,0 @@
#!/bin/bash
set -e
if [ "$UID" -ne 0 ]; then
>&2 echo 'Must be run as root'
exit 1
fi
if [ -z "$1" ]; then
>&2 echo "usage: $0 <SQUASHFS>"
exit 1
fi
VERSION=$(unsquashfs -cat $1 /usr/lib/startos/VERSION.txt)
GIT_HASH=$(unsquashfs -cat $1 /usr/lib/startos/GIT_HASH.txt)
B3SUM=$(b3sum $1 | head -c 32)
if [ -n "$CHECKSUM" ] && [ "$CHECKSUM" != "$B3SUM" ]; then
>&2 echo "CHECKSUM MISMATCH"
exit 2
fi
mv $1 /media/startos/images/${B3SUM}.rootfs
ln -rsf /media/startos/images/${B3SUM}.rootfs /media/startos/config/current.rootfs
unsquashfs -n -f -d / /media/startos/images/${B3SUM}.rootfs boot
umount -R /media/startos/next 2> /dev/null || true
umount -R /media/startos/lower 2> /dev/null || true
umount -R /media/startos/upper 2> /dev/null || true
rm -rf /media/startos/lower /media/startos/upper /media/startos/next
mkdir /media/startos/upper
mount -t tmpfs tmpfs /media/startos/upper
mkdir -p /media/startos/lower /media/startos/upper/data /media/startos/upper/work /media/startos/next
mount /media/startos/images/${B3SUM}.rootfs /media/startos/lower
mount -t overlay \
-olowerdir=/media/startos/lower,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \
overlay /media/startos/next
mkdir -p /media/startos/next/media/startos/root
mount --bind /media/startos/root /media/startos/next/media/startos/root
mkdir -p /media/startos/next/dev
mkdir -p /media/startos/next/sys
mkdir -p /media/startos/next/proc
mkdir -p /media/startos/next/boot
mount --bind /dev /media/startos/next/dev
mount --bind /sys /media/startos/next/sys
mount --bind /proc /media/startos/next/proc
mount --bind /boot /media/startos/next/boot
chroot /media/startos/next update-grub2
umount -R /media/startos/next
umount -R /media/startos/upper
umount -R /media/startos/lower
rm -rf /media/startos/lower /media/startos/upper /media/startos/next
sync
reboot

View File

@@ -38,7 +38,7 @@
}, },
"../sdk/dist": { "../sdk/dist": {
"name": "@start9labs/start-sdk", "name": "@start9labs/start-sdk",
"version": "0.4.0-beta.42", "version": "0.4.0-beta.43",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@iarna/toml": "^3.0.0", "@iarna/toml": "^3.0.0",

View File

@@ -9,7 +9,7 @@ if [ "$ARCH" = "riscv64" ]; then
RUST_ARCH="riscv64gc" RUST_ARCH="riscv64gc"
fi fi
if mountpoint -q tmp/combined; then sudo umount -R tmp/combined; fi if mountpoint -q tmp/combined; then sudo umount -l tmp/combined; fi
if mountpoint -q tmp/lower; then sudo umount tmp/lower; fi if mountpoint -q tmp/lower; then sudo umount tmp/lower; fi
sudo rm -rf tmp sudo rm -rf tmp
mkdir -p tmp/lower tmp/upper tmp/work tmp/combined mkdir -p tmp/lower tmp/upper tmp/work tmp/combined

37
core/Cargo.lock generated
View File

@@ -3458,7 +3458,7 @@ dependencies = [
"lazy_async_pool", "lazy_async_pool",
"models", "models",
"pin-project", "pin-project",
"rpc-toolkit", "rpc-toolkit 0.3.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=master)",
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
@@ -4835,7 +4835,7 @@ dependencies = [
"rand 0.9.2", "rand 0.9.2",
"regex", "regex",
"reqwest", "reqwest",
"rpc-toolkit", "rpc-toolkit 0.3.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=master)",
"rustls 0.23.35", "rustls 0.23.35",
"serde", "serde",
"serde_json", "serde_json",
@@ -6744,6 +6744,34 @@ dependencies = [
"yajrc", "yajrc",
] ]
[[package]]
name = "rpc-toolkit"
version = "0.3.2"
source = "git+https://github.com/Start9Labs/rpc-toolkit.git?rev=068db90#068db905ee38a7da97cc4a43b806409204e73723"
dependencies = [
"async-stream",
"async-trait",
"axum 0.8.6",
"clap",
"futures",
"http",
"http-body-util",
"imbl-value 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.14.0",
"lazy_format",
"lazy_static",
"openssl",
"pin-project",
"reqwest",
"serde",
"serde_json",
"thiserror 2.0.17",
"tokio",
"tokio-stream",
"url",
"yajrc",
]
[[package]] [[package]]
name = "rsa" name = "rsa"
version = "0.9.8" version = "0.9.8"
@@ -7880,7 +7908,7 @@ dependencies = [
[[package]] [[package]]
name = "start-os" name = "start-os"
version = "0.4.0-alpha.12" version = "0.4.0-alpha.13"
dependencies = [ dependencies = [
"aes 0.7.5", "aes 0.7.5",
"arti-client", "arti-client",
@@ -7888,7 +7916,6 @@ dependencies = [
"async-compression", "async-compression",
"async-stream", "async-stream",
"async-trait", "async-trait",
"aws-lc-sys",
"axum 0.8.6", "axum 0.8.6",
"backtrace-on-stack-overflow", "backtrace-on-stack-overflow",
"barrage", "barrage",
@@ -7981,7 +8008,7 @@ dependencies = [
"reqwest", "reqwest",
"reqwest_cookie_store", "reqwest_cookie_store",
"rpassword", "rpassword",
"rpc-toolkit", "rpc-toolkit 0.3.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?rev=068db90)",
"rust-argon2", "rust-argon2",
"safelog", "safelog",
"semver", "semver",

View File

@@ -2,12 +2,19 @@
cd "$(dirname "${BASH_SOURCE[0]}")" cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea set -ea
shopt -s expand_aliases shopt -s expand_aliases
PROFILE=${PROFILE:-release} PROFILE=${PROFILE:-release}
if [ "${PROFILE}" = "release" ]; then if [ "${PROFILE}" = "release" ]; then
BUILD_FLAGS="--release" BUILD_FLAGS="--release"
else
if [ "$PROFILE" != "debug"]; then
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
PROFILE=debug
fi
fi fi
if [ -z "${ARCH:-}" ]; then if [ -z "${ARCH:-}" ]; then
@@ -18,15 +25,20 @@ if [ "$ARCH" = "arm64" ]; then
ARCH="aarch64" ARCH="aarch64"
fi fi
RUST_ARCH="$ARCH"
if [ "$ARCH" = "riscv64" ]; then
RUST_ARCH="riscv64gc"
fi
if [ -z "${KERNEL_NAME:-}" ]; then if [ -z "${KERNEL_NAME:-}" ]; then
KERNEL_NAME=$(uname -s) KERNEL_NAME=$(uname -s)
fi fi
if [ -z "${TARGET:-}" ]; then if [ -z "${TARGET:-}" ]; then
if [ "$KERNEL_NAME" = "Linux" ]; then if [ "$KERNEL_NAME" = "Linux" ]; then
TARGET="$ARCH-unknown-linux-musl" TARGET="$RUST_ARCH-unknown-linux-musl"
elif [ "$KERNEL_NAME" = "Darwin" ]; then elif [ "$KERNEL_NAME" = "Darwin" ]; then
TARGET="$ARCH-apple-darwin" TARGET="$RUST_ARCH-apple-darwin"
else else
>&2 echo "unknown kernel $KERNEL_NAME" >&2 echo "unknown kernel $KERNEL_NAME"
exit 1 exit 1
@@ -53,4 +65,7 @@ fi
echo "FEATURES=\"$FEATURES\"" echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\"" echo "RUSTFLAGS=\"$RUSTFLAGS\""
cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features $FEATURE_ARGS --locked --bin start-cli --target=$TARGET rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features $FEATURE_ARGS --locked --bin start-cli --target=$TARGET
if [ "$(ls -nd "core/target/$TARGET/$PROFILE/start-cli" | awk '{ print $3 }')" != "$UID" ]; then
rust-zig-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
fi

View File

@@ -2,12 +2,19 @@
cd "$(dirname "${BASH_SOURCE[0]}")" cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea set -ea
shopt -s expand_aliases shopt -s expand_aliases
PROFILE=${PROFILE:-release} PROFILE=${PROFILE:-release}
if [ "${PROFILE}" = "release" ]; then if [ "${PROFILE}" = "release" ]; then
BUILD_FLAGS="--release" BUILD_FLAGS="--release"
else
if [ "$PROFILE" != "debug"]; then
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
PROFILE=debug
fi
fi fi
if [ -z "$ARCH" ]; then if [ -z "$ARCH" ]; then
@@ -33,4 +40,7 @@ fi
echo "FEATURES=\"$FEATURES\"" echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\"" echo "RUSTFLAGS=\"$RUSTFLAGS\""
cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-container,$FEATURES --locked --bin containerbox --target=$RUST_ARCH-unknown-linux-musl rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-container,$FEATURES --locked --bin containerbox --target=$RUST_ARCH-unknown-linux-musl
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/containerbox" | awk '{ print $3 }')" != "$UID" ]; then
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
fi

View File

@@ -2,12 +2,19 @@
cd "$(dirname "${BASH_SOURCE[0]}")" cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea set -ea
shopt -s expand_aliases shopt -s expand_aliases
PROFILE=${PROFILE:-release} PROFILE=${PROFILE:-release}
if [ "${PROFILE}" = "release" ]; then if [ "${PROFILE}" = "release" ]; then
BUILD_FLAGS="--release" BUILD_FLAGS="--release"
else
if [ "$PROFILE" != "debug"]; then
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
PROFILE=debug
fi
fi fi
if [ -z "$ARCH" ]; then if [ -z "$ARCH" ]; then
@@ -33,4 +40,7 @@ fi
echo "FEATURES=\"$FEATURES\"" echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\"" echo "RUSTFLAGS=\"$RUSTFLAGS\""
cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-registry,registry,$FEATURES --locked --bin registrybox --target=$RUST_ARCH-unknown-linux-musl rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-registry,registry,$FEATURES --locked --bin registrybox --target=$RUST_ARCH-unknown-linux-musl
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/registrybox" | awk '{ print $3 }')" != "$UID" ]; then
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
fi

View File

@@ -2,12 +2,19 @@
cd "$(dirname "${BASH_SOURCE[0]}")" cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea set -ea
shopt -s expand_aliases shopt -s expand_aliases
PROFILE=${PROFILE:-release} PROFILE=${PROFILE:-release}
if [ "${PROFILE}" = "release" ]; then if [ "${PROFILE}" = "release" ]; then
BUILD_FLAGS="--release" BUILD_FLAGS="--release"
else
if [ "$PROFILE" != "debug"]; then
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
PROFILE=debug
fi
fi fi
if [ -z "$ARCH" ]; then if [ -z "$ARCH" ]; then
@@ -33,4 +40,7 @@ fi
echo "FEATURES=\"$FEATURES\"" echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\"" echo "RUSTFLAGS=\"$RUSTFLAGS\""
cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli,startd,$FEATURES --locked --bin startbox --target=$RUST_ARCH-unknown-linux-musl rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli,startd,$FEATURES --locked --bin startbox --target=$RUST_ARCH-unknown-linux-musl
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/startbox" | awk '{ print $3 }')" != "$UID" ]; then
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
fi

View File

@@ -2,12 +2,19 @@
cd "$(dirname "${BASH_SOURCE[0]}")" cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea set -ea
shopt -s expand_aliases shopt -s expand_aliases
PROFILE=${PROFILE:-release} PROFILE=${PROFILE:-release}
if [ "${PROFILE}" = "release" ]; then if [ "${PROFILE}" = "release" ]; then
BUILD_FLAGS="--release" BUILD_FLAGS="--release"
else
if [ "$PROFILE" != "debug"]; then
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
PROFILE=debug
fi
fi fi
if [ -z "$ARCH" ]; then if [ -z "$ARCH" ]; then
@@ -31,4 +38,7 @@ if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
fi fi
echo "FEATURES=\"$FEATURES\"" echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\"" echo "RUSTFLAGS=\"$RUSTFLAGS\""
cross test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features test,$FEATURES --locked 'export_bindings_' rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features test,$FEATURES --locked 'export_bindings_'
if [ "$(ls -nd "core/startos/bindings" | awk '{ print $3 }')" != "$UID" ]; then
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID core/startos/bindings && chown -R $UID:$UID /root/.cargo"
fi

View File

@@ -2,12 +2,19 @@
cd "$(dirname "${BASH_SOURCE[0]}")" cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea set -ea
shopt -s expand_aliases shopt -s expand_aliases
PROFILE=${PROFILE:-release} PROFILE=${PROFILE:-release}
if [ "${PROFILE}" = "release" ]; then if [ "${PROFILE}" = "release" ]; then
BUILD_FLAGS="--release" BUILD_FLAGS="--release"
else
if [ "$PROFILE" != "debug"]; then
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
PROFILE=debug
fi
fi fi
if [ -z "$ARCH" ]; then if [ -z "$ARCH" ]; then
@@ -33,4 +40,7 @@ fi
echo "FEATURES=\"$FEATURES\"" echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\"" echo "RUSTFLAGS=\"$RUSTFLAGS\""
cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-tunnel,tunnel,$FEATURES --locked --bin tunnelbox --target=$RUST_ARCH-unknown-linux-musl rust-zig-builder cargo zigbuild --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features cli-tunnel,tunnel,$FEATURES --locked --bin tunnelbox --target=$RUST_ARCH-unknown-linux-musl
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/tunnelbox" | awk '{ print $3 }')" != "$UID" ]; then
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
fi

View File

@@ -1,3 +1,8 @@
#!/bin/bash #!/bin/bash
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -e SCCACHE_GHA_ENABLED -e SCCACHE_GHA_VERSION -e ACTIONS_RESULTS_URL -e ACTIONS_RUNTIME_TOKEN -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$HOME/.cache/sccache":/root/.cache/sccache -v "$(pwd)":/home/rust/src -w /home/rust/src -P start9/rust-musl-cross:$ARCH-musl' USE_TTY=
if tty -s; then
USE_TTY="-it"
fi
alias 'rust-zig-builder'='docker run '"$USE_TTY"' --rm -e "RUSTFLAGS=$RUSTFLAGS" -e "CFLAGS=-D_FORTIFY_SOURCE=2" -e "CXXFLAGS=-D_FORTIFY_SOURCE=2" -e SCCACHE_GHA_ENABLED -e SCCACHE_GHA_VERSION -e ACTIONS_RESULTS_URL -e ACTIONS_RUNTIME_TOKEN -v "$HOME/.cargo/registry":/usr/local/cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$HOME/.cache/sccache":/root/.cache/sccache -v "$(pwd)":/workdir -w /workdir -P start9/cargo-zigbuild'

View File

@@ -2,12 +2,19 @@
cd "$(dirname "${BASH_SOURCE[0]}")" cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea set -ea
shopt -s expand_aliases shopt -s expand_aliases
PROFILE=${PROFILE:-release} PROFILE=${PROFILE:-release}
if [ "${PROFILE}" = "release" ]; then if [ "${PROFILE}" = "release" ]; then
BUILD_FLAGS="--release" BUILD_FLAGS="--release"
else
if [ "$PROFILE" != "debug"]; then
>&2 echo "Unknonw profile $PROFILE: falling back to debug..."
PROFILE=debug
fi
fi fi
if [ -z "$ARCH" ]; then if [ -z "$ARCH" ]; then
@@ -31,8 +38,8 @@ if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
RUSTFLAGS="--cfg tokio_unstable" RUSTFLAGS="--cfg tokio_unstable"
fi fi
source ./core/builder-alias.sh
echo "FEATURES=\"$FEATURES\"" echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\"" echo "RUSTFLAGS=\"$RUSTFLAGS\""
cross test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=test,$FEATURES --workspace --locked --target=$ARCH-unknown-linux-musl -- --skip export_bindings_ rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=test,$FEATURES --workspace --locked -- --skip export_bindings_
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"

View File

@@ -15,7 +15,7 @@ license = "MIT"
name = "start-os" name = "start-os"
readme = "README.md" readme = "README.md"
repository = "https://github.com/Start9Labs/start-os" repository = "https://github.com/Start9Labs/start-os"
version = "0.4.0-alpha.12" # VERSION_BUMP version = "0.4.0-alpha.13" # VERSION_BUMP
[lib] [lib]
name = "startos" name = "startos"
@@ -93,7 +93,6 @@ async-compression = { version = "0.4.32", features = [
] } ] }
async-stream = "0.3.5" async-stream = "0.3.5"
async-trait = "0.1.74" async-trait = "0.1.74"
aws-lc-sys = { version = "0.32", features = ["bindgen"] }
axum = { version = "0.8.4", features = ["ws"] } axum = { version = "0.8.4", features = ["ws"] }
backtrace-on-stack-overflow = { version = "0.3.0", optional = true } backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
barrage = "0.2.3" barrage = "0.2.3"
@@ -223,7 +222,7 @@ regex = "1.10.2"
reqwest = { version = "0.12.4", features = ["json", "socks", "stream"] } reqwest = { version = "0.12.4", features = ["json", "socks", "stream"] }
reqwest_cookie_store = "0.8.0" reqwest_cookie_store = "0.8.0"
rpassword = "7.2.0" rpassword = "7.2.0"
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" } rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", rev = "068db90" }
rust-argon2 = "2.0.0" rust-argon2 = "2.0.0"
safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true } safelog = { version = "0.4.8", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
semver = { version = "1.0.20", features = ["serde"] } semver = { version = "1.0.20", features = ["serde"] }
@@ -252,7 +251,7 @@ termion = "4.0.5"
textwrap = "0.16.1" textwrap = "0.16.1"
thiserror = "2.0.12" thiserror = "2.0.12"
tokio = { version = "1.38.1", features = ["full"] } tokio = { version = "1.38.1", features = ["full"] }
tokio-rustls = "0.26.0" tokio-rustls = "0.26.4"
tokio-stream = { version = "0.1.14", features = ["io-util", "net", "sync"] } tokio-stream = { version = "0.1.14", features = ["io-util", "net", "sync"] }
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" } tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
tokio-tungstenite = { version = "0.26.2", features = ["native-tls", "url"] } tokio-tungstenite = { version = "0.26.2", features = ["native-tls", "url"] }

View File

@@ -22,7 +22,7 @@ use crate::tunnel::tunnel_router;
use crate::tunnel::web::TunnelCertHandler; use crate::tunnel::web::TunnelCertHandler;
use crate::util::logger::LOGGER; use crate::util::logger::LOGGER;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum WebserverListener { enum WebserverListener {
Http, Http,
Https(SocketAddr), Https(SocketAddr),

View File

@@ -48,7 +48,6 @@ pub async fn bind<P0: AsRef<Path>, P1: AsRef<Path>>(
pub async fn unmount<P: AsRef<Path>>(mountpoint: P, lazy: bool) -> Result<(), Error> { pub async fn unmount<P: AsRef<Path>>(mountpoint: P, lazy: bool) -> Result<(), Error> {
tracing::debug!("Unmounting {}.", mountpoint.as_ref().display()); tracing::debug!("Unmounting {}.", mountpoint.as_ref().display());
let mut cmd = tokio::process::Command::new("umount"); let mut cmd = tokio::process::Command::new("umount");
cmd.arg("-R");
if lazy { if lazy {
cmd.arg("-l"); cmd.arg("-l");
} }

View File

@@ -280,6 +280,9 @@ pub async fn list(os: &OsPartitionInfo) -> Result<Vec<DiskInfo>, Error> {
.try_fold( .try_fold(
BTreeMap::<PathBuf, DiskIndex>::new(), BTreeMap::<PathBuf, DiskIndex>::new(),
|mut disks, dir_entry| async move { |mut disks, dir_entry| async move {
if dir_entry.file_type().await?.is_dir() {
return Ok(disks);
}
if let Some(disk_path) = dir_entry.path().file_name().and_then(|s| s.to_str()) { if let Some(disk_path) = dir_entry.path().file_name().and_then(|s| s.to_str()) {
let (disk_path, part_path) = if let Some(end) = PARTITION_REGEX.find(disk_path) { let (disk_path, part_path) = if let Some(end) = PARTITION_REGEX.find(disk_path) {
( (

View File

@@ -26,6 +26,7 @@ use crate::context::{CliContext, RpcContext};
use crate::db::model::Database; use crate::db::model::Database;
use crate::db::model::public::AcmeSettings; use crate::db::model::public::AcmeSettings;
use crate::db::{DbAccess, DbAccessByKey, DbAccessMut}; use crate::db::{DbAccess, DbAccessByKey, DbAccessMut};
use crate::net::ssl::should_use_cert;
use crate::net::tls::{SingleCertResolver, TlsHandler}; use crate::net::tls::{SingleCertResolver, TlsHandler};
use crate::net::web_server::Accept; use crate::net::web_server::Accept;
use crate::prelude::*; use crate::prelude::*;
@@ -63,20 +64,27 @@ where
.and_then(|p| p.as_idx(JsonKey::new_ref(san_info))) .and_then(|p| p.as_idx(JsonKey::new_ref(san_info)))
{ {
let cert = cert.de().log_err()?; let cert = cert.de().log_err()?;
return Some( if cert
CertifiedKey::from_der( .fullchain
cert.fullchain .get(0)
.into_iter() .and_then(|c| should_use_cert(&c.0).log_err())
.map(|c| Ok(CertificateDer::from(c.to_der()?))) .unwrap_or(false)
.collect::<Result<_, Error>>() {
.log_err()?, return Some(
PrivateKeyDer::from(PrivatePkcs8KeyDer::from( CertifiedKey::from_der(
cert.key.0.private_key_to_pkcs8().log_err()?, cert.fullchain
)), .into_iter()
&*self.crypto_provider, .map(|c| Ok(CertificateDer::from(c.to_der()?)))
) .collect::<Result<_, Error>>()
.log_err()?, .log_err()?,
); PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
cert.key.0.private_key_to_pkcs8().log_err()?,
)),
&*self.crypto_provider,
)
.log_err()?,
);
}
} }
if !self.in_progress.send_if_modified(|x| { if !self.in_progress.send_if_modified(|x| {
@@ -307,6 +315,16 @@ where
return Ok(None); return Ok(None);
}; };
let cert = cert.de()?; let cert = cert.de()?;
if !cert
.fullchain
.get(0)
.map(|c| should_use_cert(&c.0))
.transpose()
.map_err(Error::from)?
.unwrap_or(false)
{
return Ok(None);
}
Ok(Some(( Ok(Some((
String::from_utf8( String::from_utf8(
cert.key cert.key

View File

@@ -437,7 +437,8 @@ impl InterfaceForwardState {
for mut entry in self.state.iter_mut() { for mut entry in self.state.iter_mut() {
entry.gc(ip_info, &self.port_forward).await?; entry.gc(ip_info, &self.port_forward).await?;
} }
Ok(())
self.port_forward.gc().await
} }
} }
@@ -537,7 +538,6 @@ impl InterfacePortForwardController {
_ = ip_info.changed() => { _ = ip_info.changed() => {
interfaces = ip_info.read(); interfaces = ip_info.read();
state.sync(&interfaces).await.log_err(); state.sync(&interfaces).await.log_err();
state.port_forward.gc().await.log_err();
} }
} }
} }

View File

@@ -1,5 +1,6 @@
use std::any::Any; use std::any::Any;
use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::fmt;
use std::future::Future; use std::future::Future;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@@ -130,7 +131,6 @@ async fn list_interfaces(
} }
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)]
struct NetworkInterfaceSetPublicParams { struct NetworkInterfaceSetPublicParams {
gateway: GatewayId, gateway: GatewayId,
public: Option<bool>, public: Option<bool>,
@@ -147,7 +147,6 @@ async fn set_public(
} }
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)]
struct UnsetPublicParams { struct UnsetPublicParams {
gateway: GatewayId, gateway: GatewayId,
} }
@@ -163,7 +162,6 @@ async fn unset_public(
} }
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)]
struct ForgetGatewayParams { struct ForgetGatewayParams {
gateway: GatewayId, gateway: GatewayId,
} }
@@ -176,7 +174,6 @@ async fn forget_iface(
} }
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)]
struct RenameGatewayParams { struct RenameGatewayParams {
id: GatewayId, id: GatewayId,
name: InternedString, name: InternedString,
@@ -404,6 +401,12 @@ async fn watcher(
) { ) {
loop { loop {
let res: Result<(), Error> = async { let res: Result<(), Error> = async {
Command::new("systemctl")
.arg("start")
.arg("NetworkManager")
.invoke(ErrorKind::Network)
.await?;
let connection = Connection::system().await?; let connection = Connection::system().await?;
let netman_proxy = NetworkManagerProxy::new(&connection).await?; let netman_proxy = NetworkManagerProxy::new(&connection).await?;
@@ -436,6 +439,11 @@ async fn watcher(
until until
.run(async { .run(async {
let devices = netman_proxy.all_devices().await?; let devices = netman_proxy.all_devices().await?;
ensure_code!(
!devices.is_empty(),
ErrorKind::Network,
"NetworkManager returned no devices. Trying again..."
);
let mut ifaces = BTreeSet::new(); let mut ifaces = BTreeSet::new();
let mut jobs = Vec::new(); let mut jobs = Vec::new();
for device in devices { for device in devices {
@@ -1538,6 +1546,14 @@ pub struct NetworkInterfaceListenerAcceptMetadata<B: Bind> {
pub inner: <B::Accept as Accept>::Metadata, pub inner: <B::Accept as Accept>::Metadata,
pub info: GatewayInfo, pub info: GatewayInfo,
} }
impl<B: Bind> fmt::Debug for NetworkInterfaceListenerAcceptMetadata<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NetworkInterfaceListenerAcceptMetadata")
.field("inner", &self.inner)
.field("info", &self.info)
.finish()
}
}
impl<B: Bind> Clone for NetworkInterfaceListenerAcceptMetadata<B> impl<B: Bind> Clone for NetworkInterfaceListenerAcceptMetadata<B>
where where
<B::Accept as Accept>::Metadata: Clone, <B::Accept as Accept>::Metadata: Clone,
@@ -1614,3 +1630,39 @@ where
Self::new(Some(Either::Left(listener))) Self::new(Some(Either::Left(listener)))
} }
} }
#[test]
fn test_filter() {
use crate::net::host::binding::NetInfo;
let wg1 = "wg1".parse::<GatewayId>().unwrap();
assert!(!InterfaceFilter::filter(
&AndFilter(
NetInfo {
private_disabled: [wg1.clone()].into_iter().collect(),
public_enabled: Default::default(),
assigned_port: None,
assigned_ssl_port: None,
},
AndFilter(IdFilter(wg1.clone()), PublicFilter { public: false }),
)
.into_dyn(),
&wg1,
&NetworkInterfaceInfo {
name: None,
public: None,
secure: None,
ip_info: Some(Arc::new(IpInfo {
name: "".into(),
scope_id: 3,
device_type: Some(NetworkInterfaceType::Wireguard),
subnets: ["10.59.0.2/24".parse::<IpNet>().unwrap()]
.into_iter()
.collect(),
lan_ip: Default::default(),
wan_ip: None,
ntp_servers: Default::default(),
dns_servers: Default::default(),
})),
},
));
}

View File

@@ -19,7 +19,7 @@ use openssl::x509::extension::{
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName, AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName,
SubjectKeyIdentifier, SubjectKeyIdentifier,
}; };
use openssl::x509::{X509, X509Builder, X509NameBuilder}; use openssl::x509::{X509, X509Builder, X509NameBuilder, X509Ref};
use openssl::*; use openssl::*;
use patch_db::HasModel; use patch_db::HasModel;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -48,6 +48,17 @@ pub fn gen_nistp256() -> Result<PKey<Private>, ErrorStack> {
)?)?) )?)?)
} }
pub fn should_use_cert(cert: &X509Ref) -> Result<bool, ErrorStack> {
Ok(cert
.not_before()
.compare(Asn1Time::days_from_now(0)?.as_ref())?
== Ordering::Less
&& cert
.not_after()
.compare(Asn1Time::days_from_now(30)?.as_ref())?
== Ordering::Greater)
}
#[derive(Debug, Deserialize, Serialize, HasModel)] #[derive(Debug, Deserialize, Serialize, HasModel)]
#[model = "Model<Self>"] #[model = "Model<Self>"]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@@ -83,30 +94,8 @@ impl Model<CertStore> {
.map(|m| m.de()) .map(|m| m.de())
.transpose()? .transpose()?
{ {
if cert_data if should_use_cert(&cert_data.certs.ed25519)?
.certs && should_use_cert(&cert_data.certs.nistp256)?
.ed25519
.not_before()
.compare(Asn1Time::days_from_now(0)?.as_ref())?
== Ordering::Less
&& cert_data
.certs
.ed25519
.not_after()
.compare(Asn1Time::days_from_now(30)?.as_ref())?
== Ordering::Greater
&& cert_data
.certs
.nistp256
.not_before()
.compare(Asn1Time::days_from_now(0)?.as_ref())?
== Ordering::Less
&& cert_data
.certs
.nistp256
.not_after()
.compare(Asn1Time::days_from_now(30)?.as_ref())?
== Ordering::Greater
{ {
return Ok(FullchainCertData { return Ok(FullchainCertData {
root: self.as_root_cert().de()?.0, root: self.as_root_cert().de()?.0,

View File

@@ -889,7 +889,8 @@ async fn torctl(
} }
} }
} }
Err(Error::new(eyre!("Log stream terminated"), ErrorKind::Tor)) // Err(Error::new(eyre!("Log stream terminated"), ErrorKind::Tor))
Ok(())
}; };
let health_checker = async { let health_checker = async {
let mut last_success = Instant::now(); let mut last_success = Instant::now();

View File

@@ -1,5 +1,6 @@
use std::any::Any; use std::any::Any;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::task::{Poll, ready}; use std::task::{Poll, ready};
@@ -41,6 +42,7 @@ use crate::net::tls::{
use crate::net::web_server::{Accept, AcceptStream, ExtractVisitor, TcpMetadata, extract}; use crate::net::web_server::{Accept, AcceptStream, ExtractVisitor, TcpMetadata, extract};
use crate::prelude::*; use crate::prelude::*;
use crate::util::collections::EqSet; use crate::util::collections::EqSet;
use crate::util::future::WeakFuture;
use crate::util::serde::{HandlerExtSerde, MaybeUtf8String, display_serializable}; use crate::util::serde::{HandlerExtSerde, MaybeUtf8String, display_serializable};
use crate::util::sync::{SyncMutex, Watch}; use crate::util::sync::{SyncMutex, Watch};
@@ -134,7 +136,6 @@ impl VHostController {
pub fn dump_table( pub fn dump_table(
&self, &self,
) -> BTreeMap<JsonKey<u16>, BTreeMap<JsonKey<Option<InternedString>>, EqSet<String>>> { ) -> BTreeMap<JsonKey<u16>, BTreeMap<JsonKey<Option<InternedString>>, EqSet<String>>> {
let ip_info = self.interfaces.watcher.ip_info();
self.servers.peek(|s| { self.servers.peek(|s| {
s.iter() s.iter()
.map(|(k, v)| { .map(|(k, v)| {
@@ -187,7 +188,7 @@ pub trait VHostTarget<A: Accept>: std::fmt::Debug + Eq {
hello: &'a ClientHello<'a>, hello: &'a ClientHello<'a>,
metadata: &'a <A as Accept>::Metadata, metadata: &'a <A as Accept>::Metadata,
) -> impl Future<Output = Option<(ServerConfig, Self::PreprocessRes)>> + Send + 'a; ) -> impl Future<Output = Option<(ServerConfig, Self::PreprocessRes)>> + Send + 'a;
fn handle_stream(&self, stream: AcceptStream, prev: Self::PreprocessRes); fn handle_stream(&self, stream: AcceptStream, prev: Self::PreprocessRes, rc: Weak<()>);
} }
pub trait DynVHostTargetT<A: Accept>: std::fmt::Debug + Any { pub trait DynVHostTargetT<A: Accept>: std::fmt::Debug + Any {
@@ -199,7 +200,7 @@ pub trait DynVHostTargetT<A: Accept>: std::fmt::Debug + Any {
hello: &'a ClientHello<'a>, hello: &'a ClientHello<'a>,
metadata: &'a <A as Accept>::Metadata, metadata: &'a <A as Accept>::Metadata,
) -> BoxFuture<'a, Option<(ServerConfig, Box<dyn Any + Send>)>>; ) -> BoxFuture<'a, Option<(ServerConfig, Box<dyn Any + Send>)>>;
fn handle_stream(&self, stream: AcceptStream, prev: Box<dyn Any + Send>); fn handle_stream(&self, stream: AcceptStream, prev: Box<dyn Any + Send>, rc: Weak<()>);
fn eq(&self, other: &dyn DynVHostTargetT<A>) -> bool; fn eq(&self, other: &dyn DynVHostTargetT<A>) -> bool;
} }
impl<A: Accept, T: VHostTarget<A> + 'static> DynVHostTargetT<A> for T { impl<A: Accept, T: VHostTarget<A> + 'static> DynVHostTargetT<A> for T {
@@ -219,9 +220,9 @@ impl<A: Accept, T: VHostTarget<A> + 'static> DynVHostTargetT<A> for T {
.map(|o| o.map(|(cfg, res)| (cfg, Box::new(res) as Box<dyn Any + Send>))) .map(|o| o.map(|(cfg, res)| (cfg, Box::new(res) as Box<dyn Any + Send>)))
.boxed() .boxed()
} }
fn handle_stream(&self, stream: AcceptStream, prev: Box<dyn Any + Send>) { fn handle_stream(&self, stream: AcceptStream, prev: Box<dyn Any + Send>, rc: Weak<()>) {
if let Ok(prev) = prev.downcast() { if let Ok(prev) = prev.downcast() {
VHostTarget::handle_stream(self, stream, *prev); VHostTarget::handle_stream(self, stream, *prev, rc);
} }
} }
fn eq(&self, other: &dyn DynVHostTargetT<A>) -> bool { fn eq(&self, other: &dyn DynVHostTargetT<A>) -> bool {
@@ -251,21 +252,27 @@ impl<A: Accept + 'static> PartialEq for DynVHostTarget<A> {
} }
} }
impl<A: Accept + 'static> Eq for DynVHostTarget<A> {} impl<A: Accept + 'static> Eq for DynVHostTarget<A> {}
struct Preprocessed<A: Accept>(DynVHostTarget<A>, Box<dyn Any + Send>); struct Preprocessed<A: Accept>(DynVHostTarget<A>, Weak<()>, Box<dyn Any + Send>);
impl<A: Accept> fmt::Debug for Preprocessed<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(self.0).0.fmt(f)
}
}
impl<A: Accept + 'static> DynVHostTarget<A> { impl<A: Accept + 'static> DynVHostTarget<A> {
async fn into_preprocessed( async fn into_preprocessed(
self, self,
rc: Weak<()>,
prev: ServerConfig, prev: ServerConfig,
hello: &ClientHello<'_>, hello: &ClientHello<'_>,
metadata: &<A as Accept>::Metadata, metadata: &<A as Accept>::Metadata,
) -> Option<(ServerConfig, Preprocessed<A>)> { ) -> Option<(ServerConfig, Preprocessed<A>)> {
let (cfg, res) = self.0.preprocess(prev, hello, metadata).await?; let (cfg, res) = self.0.preprocess(prev, hello, metadata).await?;
Some((cfg, Preprocessed(self, res))) Some((cfg, Preprocessed(self, rc, res)))
} }
} }
impl<A: Accept + 'static> Preprocessed<A> { impl<A: Accept + 'static> Preprocessed<A> {
fn finish(self, stream: AcceptStream) { fn finish(self, stream: AcceptStream) {
(self.0).0.handle_stream(stream, self.1); (self.0).0.handle_stream(stream, self.2, self.1);
} }
} }
@@ -279,6 +286,7 @@ pub struct ProxyTarget {
impl PartialEq for ProxyTarget { impl PartialEq for ProxyTarget {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.filter == other.filter self.filter == other.filter
&& self.acme == other.acme
&& self.addr == other.addr && self.addr == other.addr
&& self.connect_ssl.as_ref().map(Arc::as_ptr) && self.connect_ssl.as_ref().map(Arc::as_ptr)
== other.connect_ssl.as_ref().map(Arc::as_ptr) == other.connect_ssl.as_ref().map(Arc::as_ptr)
@@ -294,6 +302,9 @@ where
type PreprocessRes = AcceptStream; type PreprocessRes = AcceptStream;
fn filter(&self, metadata: &<A as Accept>::Metadata) -> bool { fn filter(&self, metadata: &<A as Accept>::Metadata) -> bool {
let info = extract::<GatewayInfo, _>(metadata); let info = extract::<GatewayInfo, _>(metadata);
if info.is_none() {
tracing::warn!("No GatewayInfo on metadata");
}
info.as_ref() info.as_ref()
.map_or(true, |i| self.filter.filter(&i.id, &i.info)) .map_or(true, |i| self.filter.filter(&i.id, &i.info))
} }
@@ -304,7 +315,7 @@ where
&'a self, &'a self,
mut prev: ServerConfig, mut prev: ServerConfig,
hello: &'a ClientHello<'a>, hello: &'a ClientHello<'a>,
metadata: &'a <A as Accept>::Metadata, _: &'a <A as Accept>::Metadata,
) -> Option<(ServerConfig, Self::PreprocessRes)> { ) -> Option<(ServerConfig, Self::PreprocessRes)> {
let tcp_stream = TcpStream::connect(self.addr) let tcp_stream = TcpStream::connect(self.addr)
.await .await
@@ -345,8 +356,10 @@ where
} }
Some((prev, Box::pin(tcp_stream))) Some((prev, Box::pin(tcp_stream)))
} }
fn handle_stream(&self, mut stream: AcceptStream, mut prev: Self::PreprocessRes) { fn handle_stream(&self, mut stream: AcceptStream, mut prev: Self::PreprocessRes, rc: Weak<()>) {
tokio::spawn(async move { tokio::io::copy_bidirectional(&mut stream, &mut prev).await }); tokio::spawn(async move {
WeakFuture::new(rc, tokio::io::copy_bidirectional(&mut stream, &mut prev)).await
});
} }
} }
@@ -436,16 +449,16 @@ where
return Some(prev); return Some(prev);
} }
let target = self.0.peek(|m| { let (target, rc) = self.0.peek(|m| {
m.get(&hello.server_name().map(InternedString::from)) m.get(&hello.server_name().map(InternedString::from))
.into_iter() .into_iter()
.flatten() .flatten()
.filter(|(_, rc)| rc.strong_count() > 0) .filter(|(_, rc)| rc.strong_count() > 0)
.find(|(t, _)| t.0.filter(metadata)) .find(|(t, _)| t.0.filter(metadata))
.map(|(e, _)| e.clone()) .map(|(t, rc)| (t.clone(), rc.clone()))
})?; })?;
let (prev, store) = target.into_preprocessed(prev, hello, metadata).await?; let (prev, store) = target.into_preprocessed(rc, prev, hello, metadata).await?;
self.1 = Some(store); self.1 = Some(store);
@@ -480,6 +493,14 @@ struct VHostListenerMetadata<A: Accept> {
inner: TlsMetadata<A::Metadata>, inner: TlsMetadata<A::Metadata>,
preprocessed: Preprocessed<A>, preprocessed: Preprocessed<A>,
} }
impl<A: Accept> fmt::Debug for VHostListenerMetadata<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VHostListenerMetadata")
.field("inner", &self.inner)
.field("preprocessed", &self.preprocessed)
.finish()
}
}
impl<M, A> Accept for VHostListener<M, A> impl<M, A> Accept for VHostListener<M, A>
where where
for<'a> M: HasModel<Model = Model<M>> for<'a> M: HasModel<Model = Model<M>>
@@ -637,6 +658,7 @@ impl<A: Accept> VHostServer<A> {
changed = true; changed = true;
Arc::new(()) Arc::new(())
}; };
targets.retain(|_, rc| rc.strong_count() > 0);
targets.insert(target, Arc::downgrade(&rc)); targets.insert(target, Arc::downgrade(&rc));
writable.insert(hostname, targets); writable.insert(hostname, targets);
res = Ok(rc); res = Ok(rc);

View File

@@ -1,3 +1,4 @@
use core::fmt;
use std::any::Any; use std::any::Any;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::future::Future; use std::future::Future;
@@ -68,7 +69,7 @@ pub fn extract<
metadata: &M, metadata: &M,
) -> Option<T> { ) -> Option<T> {
let mut visitor = ExtractVisitor(None); let mut visitor = ExtractVisitor(None);
visitor.visit(metadata); metadata.visit(&mut visitor);
visitor.0 visitor.0
} }
@@ -84,7 +85,7 @@ impl<V: MetadataVisitor> Visit<V> for TcpMetadata {
} }
pub trait Accept { pub trait Accept {
type Metadata; type Metadata: fmt::Debug;
fn poll_accept( fn poll_accept(
&mut self, &mut self,
cx: &mut std::task::Context<'_>, cx: &mut std::task::Context<'_>,
@@ -144,7 +145,7 @@ where
} }
} }
#[derive(Clone, VisitFields)] #[derive(Debug, Clone, VisitFields)]
pub struct MapListenerMetadata<K, M> { pub struct MapListenerMetadata<K, M> {
pub inner: M, pub inner: M,
pub key: K, pub key: K,
@@ -162,7 +163,7 @@ where
impl<K, A> Accept for BTreeMap<K, A> impl<K, A> Accept for BTreeMap<K, A>
where where
K: Clone, K: Clone + fmt::Debug,
A: Accept, A: Accept,
{ {
type Metadata = MapListenerMetadata<K, A::Metadata>; type Metadata = MapListenerMetadata<K, A::Metadata>;
@@ -218,40 +219,38 @@ trait DynAcceptT: Send + Sync {
fn poll_accept( fn poll_accept(
&mut self, &mut self,
cx: &mut std::task::Context<'_>, cx: &mut std::task::Context<'_>,
) -> Poll< ) -> Poll<Result<(DynMetadata, AcceptStream), Error>>;
Result<
(
Box<dyn for<'a> Visit<ExtensionVisitor<'a>> + Send + Sync>,
AcceptStream,
),
Error,
>,
>;
} }
impl<A> DynAcceptT for A impl<A> DynAcceptT for A
where where
A: Accept + Send + Sync, A: Accept + Send + Sync,
for<'a> <A as Accept>::Metadata: Visit<ExtensionVisitor<'a>> + Send + Sync + 'static, <A as Accept>::Metadata: DynMetadataT + 'static,
{ {
fn poll_accept( fn poll_accept(
&mut self, &mut self,
cx: &mut std::task::Context<'_>, cx: &mut std::task::Context<'_>,
) -> Poll< ) -> Poll<Result<(DynMetadata, AcceptStream), Error>> {
Result<
(
Box<dyn for<'a> Visit<ExtensionVisitor<'a>> + Send + Sync>,
AcceptStream,
),
Error,
>,
> {
let (metadata, stream) = ready!(Accept::poll_accept(self, cx)?); let (metadata, stream) = ready!(Accept::poll_accept(self, cx)?);
Poll::Ready(Ok((Box::new(metadata), stream))) Poll::Ready(Ok((DynMetadata(Box::new(metadata)), stream)))
} }
} }
pub struct DynAccept(Box<dyn DynAcceptT>); pub struct DynAccept(Box<dyn DynAcceptT>);
trait DynMetadataT: for<'a> Visit<ExtensionVisitor<'a>> + fmt::Debug + Send + Sync {}
impl<T> DynMetadataT for T where for<'a> T: Visit<ExtensionVisitor<'a>> + fmt::Debug + Send + Sync {}
#[derive(Debug)]
pub struct DynMetadata(Box<dyn DynMetadataT>);
impl<'a> Visit<ExtensionVisitor<'a>> for DynMetadata {
fn visit(
&self,
visitor: &mut ExtensionVisitor<'a>,
) -> <ExtensionVisitor<'a> as Visitor>::Result {
self.0.visit(visitor)
}
}
impl Accept for DynAccept { impl Accept for DynAccept {
type Metadata = Box<dyn for<'a> Visit<ExtensionVisitor<'a>> + Send + Sync>; type Metadata = DynMetadata;
fn poll_accept( fn poll_accept(
&mut self, &mut self,
cx: &mut std::task::Context<'_>, cx: &mut std::task::Context<'_>,
@@ -325,7 +324,7 @@ impl Acceptor<Vec<DynAccept>> {
} }
impl<K> Acceptor<BTreeMap<K, TcpListener>> impl<K> Acceptor<BTreeMap<K, TcpListener>>
where where
K: Ord + Clone + Send + Sync + 'static, K: Ord + Clone + fmt::Debug + Send + Sync + 'static,
{ {
pub async fn bind_map( pub async fn bind_map(
listen: impl IntoIterator<Item = (K, SocketAddr)>, listen: impl IntoIterator<Item = (K, SocketAddr)>,
@@ -347,7 +346,7 @@ where
} }
impl<K> Acceptor<BTreeMap<K, DynAccept>> impl<K> Acceptor<BTreeMap<K, DynAccept>>
where where
K: Ord + Clone + Send + Sync + 'static, K: Ord + Clone + fmt::Debug + Send + Sync + 'static,
{ {
pub async fn bind_map_dyn( pub async fn bind_map_dyn(
listen: impl IntoIterator<Item = (K, SocketAddr)>, listen: impl IntoIterator<Item = (K, SocketAddr)>,

View File

@@ -356,7 +356,10 @@ pub async fn execute<C: Context>(
let mut install = Command::new("chroot"); let mut install = Command::new("chroot");
install.arg(overlay.path()).arg("grub-install"); install.arg(overlay.path()).arg("grub-install");
if tokio::fs::metadata("/sys/firmware/efi").await.is_err() { if tokio::fs::metadata("/sys/firmware/efi").await.is_err() {
install.arg("--target=i386-pc"); match ARCH {
"x86_64" => install.arg("--target=i386-pc"),
_ => &mut install,
};
} else { } else {
match ARCH { match ARCH {
"x86_64" => install.arg("--target=x86_64-efi"), "x86_64" => install.arg("--target=x86_64-efi"),
@@ -372,7 +375,7 @@ pub async fn execute<C: Context>(
Command::new("chroot") Command::new("chroot")
.arg(overlay.path()) .arg(overlay.path())
.arg("update-grub2") .arg("update-grub")
.invoke(crate::ErrorKind::Grub) .invoke(crate::ErrorKind::Grub)
.await?; .await?;
dev.unmount(false).await?; dev.unmount(false).await?;

View File

@@ -150,31 +150,39 @@ impl ExecParams {
cmd.env(k, v); cmd.env(k, v);
} }
if let Some(uid) = user.as_deref().and_then(|u| u.parse::<u32>().ok()) { if let Some((uid, gid)) =
cmd.uid(uid); if let Some(uid) = user.as_deref().and_then(|u| u.parse::<u32>().ok()) {
} else if let Some(user) = user { Some((uid, uid))
let passwd = std::fs::read_to_string("/etc/passwd") } else if let Some(user) = user {
.with_ctx(|_| (ErrorKind::Filesystem, "read /etc/passwd")); let passwd = std::fs::read_to_string("/etc/passwd")
if passwd.is_err() && user == "root" { .with_ctx(|_| (ErrorKind::Filesystem, "read /etc/passwd"));
cmd.uid(0); Some(if passwd.is_err() && user == "root" {
cmd.gid(0); (0, 0)
} else {
let (uid, gid) = passwd?
.lines()
.find_map(|l| {
let mut split = l.trim().split(":");
if user != split.next()? {
return None;
}
split.next(); // throw away x
Some((split.next()?.parse().ok()?, split.next()?.parse().ok()?))
// uid gid
})
.or_not_found(lazy_format!("{user} in /etc/passwd"))?;
(uid, gid)
})
} else { } else {
let (uid, gid) = passwd? None
.lines()
.find_map(|l| {
let mut split = l.trim().split(":");
if user != split.next()? {
return None;
}
split.next(); // throw away x
Some((split.next()?.parse().ok()?, split.next()?.parse().ok()?))
// uid gid
})
.or_not_found(lazy_format!("{user} in /etc/passwd"))?;
cmd.uid(uid);
cmd.gid(gid);
} }
}; {
std::os::unix::fs::chown("/proc/self/fd/0", Some(uid), Some(gid)).log_err();
std::os::unix::fs::chown("/proc/self/fd/1", Some(uid), Some(gid)).log_err();
std::os::unix::fs::chown("/proc/self/fd/2", Some(uid), Some(gid)).log_err();
cmd.uid(uid);
cmd.gid(gid);
}
if let Some(workdir) = workdir { if let Some(workdir) = workdir {
cmd.current_dir(workdir); cmd.current_dir(workdir);
} else { } else {

View File

@@ -725,6 +725,8 @@ pub struct AttachParams {
name: Option<InternedString>, name: Option<InternedString>,
#[ts(type = "string | null")] #[ts(type = "string | null")]
image_id: Option<ImageId>, image_id: Option<ImageId>,
#[ts(type = "string | null")]
user: Option<InternedString>,
} }
pub async fn attach( pub async fn attach(
ctx: RpcContext, ctx: RpcContext,
@@ -738,6 +740,7 @@ pub async fn attach(
subcontainer, subcontainer,
image_id, image_id,
name, name,
user,
}: AttachParams, }: AttachParams,
) -> Result<Guid, Error> { ) -> Result<Guid, Error> {
let (container_id, subcontainer_id, image_id, workdir, root_command) = { let (container_id, subcontainer_id, image_id, workdir, root_command) = {
@@ -814,9 +817,26 @@ pub async fn attach(
.join("etc") .join("etc")
.join("passwd"); .join("passwd");
let root_command = get_passwd_root_command(passwd).await; let image_meta = serde_json::from_str::<Value>(
&tokio::fs::read_to_string(
root_dir
.join("media/startos/images/")
.join(&image_id)
.with_extension("json"),
)
.await?,
)
.with_kind(ErrorKind::Deserialization)?;
let workdir = attach_workdir(&image_id, &root_dir).await?; let root_command = get_passwd_command(
passwd,
user.as_deref()
.or_else(|| image_meta["user"].as_str())
.unwrap_or("root"),
)
.await;
let workdir = image_meta["workdir"].as_str().map(|s| s.to_owned());
if subcontainer_ids.len() > 1 { if subcontainer_ids.len() > 1 {
let subcontainer_ids = subcontainer_ids let subcontainer_ids = subcontainer_ids
@@ -849,6 +869,7 @@ pub async fn attach(
pty_size: Option<TermSize>, pty_size: Option<TermSize>,
image_id: ImageId, image_id: ImageId,
workdir: Option<String>, workdir: Option<String>,
user: Option<InternedString>,
root_command: &RootCommand, root_command: &RootCommand,
) -> Result<(), Error> { ) -> Result<(), Error> {
use axum::extract::ws::Message; use axum::extract::ws::Message;
@@ -871,6 +892,10 @@ pub async fn attach(
.with_extension("env"), .with_extension("env"),
); );
if let Some(user) = user {
cmd.arg("--user").arg(&*user);
}
if let Some(workdir) = workdir { if let Some(workdir) = workdir {
cmd.arg("--workdir").arg(workdir); cmd.arg("--workdir").arg(workdir);
} }
@@ -1032,6 +1057,7 @@ pub async fn attach(
pty_size, pty_size,
image_id, image_id,
workdir, workdir,
user,
&root_command, &root_command,
) )
.await .await
@@ -1051,19 +1077,46 @@ pub async fn attach(
Ok(guid) Ok(guid)
} }
async fn attach_workdir(image_id: &ImageId, root_dir: &Path) -> Result<Option<String>, Error> { #[derive(Deserialize, Serialize, TS)]
let path_str = root_dir.join("media/startos/images/"); #[serde(rename_all = "camelCase")]
pub struct ListSubcontainersParams {
let mut subcontainer_json = pub id: PackageId,
tokio::fs::File::open(path_str.join(image_id).with_extension("json")).await?;
let mut contents = vec![];
subcontainer_json.read_to_end(&mut contents).await?;
let subcontainer_json: serde_json::Value =
serde_json::from_slice(&contents).with_kind(ErrorKind::Filesystem)?;
Ok(subcontainer_json["workdir"].as_str().map(|x| x.to_string()))
} }
async fn get_passwd_root_command(etc_passwd_path: PathBuf) -> RootCommand { #[derive(Clone, Debug, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
pub struct SubcontainerInfo {
pub name: InternedString,
pub image_id: ImageId,
}
pub async fn list_subcontainers(
ctx: RpcContext,
ListSubcontainersParams { id }: ListSubcontainersParams,
) -> Result<BTreeMap<Guid, SubcontainerInfo>, Error> {
let service = ctx.services.get(&id).await;
let service_ref = service.as_ref().or_not_found(&id)?;
let container = &service_ref.seed.persistent_container;
let subcontainers = container.subcontainers.lock().await;
let result: BTreeMap<Guid, SubcontainerInfo> = subcontainers
.iter()
.map(|(guid, subcontainer)| {
(
guid.clone(),
SubcontainerInfo {
name: subcontainer.name.clone(),
image_id: subcontainer.image_id.clone(),
},
)
})
.collect();
Ok(result)
}
async fn get_passwd_command(etc_passwd_path: PathBuf, user: &str) -> RootCommand {
async { async {
let mut file = tokio::fs::File::open(etc_passwd_path).await?; let mut file = tokio::fs::File::open(etc_passwd_path).await?;
@@ -1074,8 +1127,8 @@ async fn get_passwd_root_command(etc_passwd_path: PathBuf) -> RootCommand {
for line in contents.split('\n') { for line in contents.split('\n') {
let line_information = line.split(':').collect::<Vec<_>>(); let line_information = line.split(':').collect::<Vec<_>>();
if let (Some(&"root"), Some(shell)) = if let (Some(&u), Some(shell)) = (line_information.first(), line_information.last())
(line_information.first(), line_information.last()) && u == user
{ {
return Ok(shell.to_string()); return Ok(shell.to_string());
} }
@@ -1106,6 +1159,8 @@ pub struct CliAttachParams {
#[arg(long, short)] #[arg(long, short)]
name: Option<InternedString>, name: Option<InternedString>,
#[arg(long, short)] #[arg(long, short)]
user: Option<InternedString>,
#[arg(long, short)]
image_id: Option<ImageId>, image_id: Option<ImageId>,
} }
#[instrument[skip_all]] #[instrument[skip_all]]
@@ -1147,6 +1202,7 @@ pub async fn cli_attach(
"subcontainer": params.subcontainer, "subcontainer": params.subcontainer,
"imageId": params.image_id, "imageId": params.image_id,
"name": params.name, "name": params.name,
"user": params.user,
}), }),
) )
.await?, .await?,

View File

@@ -353,6 +353,7 @@ pub async fn show_config(
Ok(client Ok(client
.client_config( .client_config(
ip, ip,
subnet,
wg.as_key().de()?.verifying_key(), wg.as_key().de()?.verifying_key(),
(wan_addr, wg.as_port().de()?).into(), (wan_addr, wg.as_port().de()?).into(),
) )

View File

@@ -293,14 +293,7 @@ pub async fn set_password_cli(
Ok(()) Ok(())
} }
pub async fn reset_password( pub async fn reset_password(ctx: CliContext) -> Result<(), Error> {
HandlerArgs {
context,
parent_method,
method,
..
}: HandlerArgs<CliContext>,
) -> Result<(), Error> {
println!("Generating a random password..."); println!("Generating a random password...");
let params = SetPasswordParams { let params = SetPasswordParams {
password: base32::encode( password: base32::encode(
@@ -309,11 +302,7 @@ pub async fn reset_password(
), ),
}; };
context ctx.call_remote::<TunnelContext>("auth.set-password", to_value(&params)?)
.call_remote::<TunnelContext>(
&parent_method.iter().chain(method.iter()).join("."),
to_value(&params)?,
)
.await?; .await?;
println!("Your new password is:"); println!("Your new password is:");

View File

@@ -7,6 +7,6 @@ PrivateKey = {privkey}
[Peer] [Peer]
PublicKey = {server_pubkey} PublicKey = {server_pubkey}
PresharedKey = {psk} PresharedKey = {psk}
AllowedIPs = 0.0.0.0/0,::/0 AllowedIPs = {subnet}
Endpoint = {server_addr} Endpoint = {server_addr}
PersistentKeepalive = 25 PersistentKeepalive = 25

View File

@@ -170,12 +170,14 @@ impl WgConfig {
pub fn client_config( pub fn client_config(
self, self,
addr: Ipv4Addr, addr: Ipv4Addr,
subnet: Ipv4Net,
server_pubkey: Base64<PublicKey>, server_pubkey: Base64<PublicKey>,
server_addr: SocketAddr, server_addr: SocketAddr,
) -> ClientConfig { ) -> ClientConfig {
ClientConfig { ClientConfig {
client_config: self, client_config: self,
client_addr: addr, client_addr: addr,
subnet,
server_pubkey, server_pubkey,
server_addr, server_addr,
} }
@@ -213,6 +215,7 @@ where
pub struct ClientConfig { pub struct ClientConfig {
client_config: WgConfig, client_config: WgConfig,
client_addr: Ipv4Addr, client_addr: Ipv4Addr,
subnet: Ipv4Net,
#[serde(deserialize_with = "deserialize_verifying_key")] #[serde(deserialize_with = "deserialize_verifying_key")]
server_pubkey: Base64<PublicKey>, server_pubkey: Base64<PublicKey>,
server_addr: SocketAddr, server_addr: SocketAddr,
@@ -226,6 +229,7 @@ impl std::fmt::Display for ClientConfig {
privkey = self.client_config.key.to_padded_string(), privkey = self.client_config.key.to_padded_string(),
psk = self.client_config.psk.to_padded_string(), psk = self.client_config.psk.to_padded_string(),
addr = self.client_addr, addr = self.client_addr,
subnet = self.subnet,
server_pubkey = self.server_pubkey.to_padded_string(), server_pubkey = self.server_pubkey.to_padded_string(),
server_addr = self.server_addr, server_addr = self.server_addr,
) )

View File

@@ -19,12 +19,6 @@ use ts_rs::TS;
use crate::PLATFORM; use crate::PLATFORM;
use crate::context::{CliContext, RpcContext}; use crate::context::{CliContext, RpcContext};
use crate::disk::mount::filesystem::MountType;
use crate::disk::mount::filesystem::bind::Bind;
use crate::disk::mount::filesystem::block_dev::BlockDev;
use crate::disk::mount::filesystem::efivarfs::EfiVarFs;
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard};
use crate::notifications::{NotificationLevel, notify}; use crate::notifications::{NotificationLevel, notify};
use crate::prelude::*; use crate::prelude::*;
use crate::progress::{ use crate::progress::{
@@ -275,7 +269,6 @@ async fn maybe_do_update(
download_phase.set_total(asset.commitment.size); download_phase.set_total(asset.commitment.size);
download_phase.set_units(Some(ProgressUnits::Bytes)); download_phase.set_units(Some(ProgressUnits::Bytes));
let reverify_phase = progress.add_phase("Reverifying File".into(), Some(10)); let reverify_phase = progress.add_phase("Reverifying File".into(), Some(10));
let sync_boot_phase = progress.add_phase("Syncing Boot Files".into(), Some(1));
let finalize_phase = progress.add_phase("Finalizing Update".into(), Some(1)); let finalize_phase = progress.add_phase("Finalizing Update".into(), Some(1));
let start_progress = progress.snapshot(); let start_progress = progress.snapshot();
@@ -331,7 +324,6 @@ async fn maybe_do_update(
prune_phase, prune_phase,
download_phase, download_phase,
reverify_phase, reverify_phase,
sync_boot_phase,
finalize_phase, finalize_phase,
}, },
) )
@@ -388,7 +380,6 @@ struct UpdateProgressHandles {
prune_phase: PhaseProgressTrackerHandle, prune_phase: PhaseProgressTrackerHandle,
download_phase: PhaseProgressTrackerHandle, download_phase: PhaseProgressTrackerHandle,
reverify_phase: PhaseProgressTrackerHandle, reverify_phase: PhaseProgressTrackerHandle,
sync_boot_phase: PhaseProgressTrackerHandle,
finalize_phase: PhaseProgressTrackerHandle, finalize_phase: PhaseProgressTrackerHandle,
} }
@@ -401,7 +392,6 @@ async fn do_update(
mut prune_phase, mut prune_phase,
mut download_phase, mut download_phase,
mut reverify_phase, mut reverify_phase,
mut sync_boot_phase,
mut finalize_phase, mut finalize_phase,
}: UpdateProgressHandles, }: UpdateProgressHandles,
) -> Result<(), Error> { ) -> Result<(), Error> {
@@ -416,9 +406,7 @@ async fn do_update(
prune_phase.complete(); prune_phase.complete();
download_phase.start(); download_phase.start();
let path = Path::new("/media/startos/images") let path = Path::new("/media/startos/images/next.squashfs");
.join(hex::encode(&asset.commitment.hash[..16]))
.with_extension("rootfs");
let mut dst = AtomicFile::new(&path, None::<&Path>) let mut dst = AtomicFile::new(&path, None::<&Path>)
.await .await
.with_kind(ErrorKind::Filesystem)?; .with_kind(ErrorKind::Filesystem)?;
@@ -438,92 +426,24 @@ async fn do_update(
dst.save().await.with_kind(ErrorKind::Filesystem)?; dst.save().await.with_kind(ErrorKind::Filesystem)?;
reverify_phase.complete(); reverify_phase.complete();
sync_boot_phase.start(); finalize_phase.start();
Command::new("unsquashfs") Command::new("unsquashfs")
.arg("-n") .arg("-n")
.arg("-f") .arg("-f")
.arg("-d") .arg("-d")
.arg("/") .arg("/")
.arg(&path) .arg(&path)
.arg("boot") .arg("/usr/lib/startos/scripts/upgrade")
.invoke(crate::ErrorKind::Filesystem) .invoke(crate::ErrorKind::Filesystem)
.await?; .await?;
if &*PLATFORM != "raspberrypi" {
let mountpoint = "/media/startos/next";
let root_guard = OverlayGuard::mount(
TmpMountGuard::mount(&BlockDev::new(&path), MountType::ReadOnly).await?,
mountpoint,
)
.await?;
let startos = MountGuard::mount(
&Bind::new("/media/startos/root"),
root_guard.path().join("media/startos/root"),
MountType::ReadOnly,
)
.await?;
let boot_guard = MountGuard::mount(
&Bind::new("/boot"),
root_guard.path().join("boot"),
MountType::ReadWrite,
)
.await?;
let dev = MountGuard::mount(
&Bind::new("/dev"),
root_guard.path().join("dev"),
MountType::ReadWrite,
)
.await?;
let proc = MountGuard::mount(
&Bind::new("/proc"),
root_guard.path().join("proc"),
MountType::ReadWrite,
)
.await?;
let sys = MountGuard::mount(
&Bind::new("/sys"),
root_guard.path().join("sys"),
MountType::ReadWrite,
)
.await?;
let efivarfs = if tokio::fs::metadata("/sys/firmware/efi").await.is_ok() {
Some(
MountGuard::mount(
&EfiVarFs,
root_guard.path().join("sys/firmware/efi/efivars"),
MountType::ReadWrite,
)
.await?,
)
} else {
None
};
Command::new("chroot") let checksum = hex::encode(&asset.commitment.hash[..16]);
.arg(root_guard.path())
.arg("update-grub2")
.invoke(ErrorKind::Grub)
.await?;
if let Some(efivarfs) = efivarfs { Command::new("/usr/lib/startos/scripts/upgrade")
efivarfs.unmount(false).await?; .env("CHECKSUM", &checksum)
}
sys.unmount(false).await?;
proc.unmount(false).await?;
dev.unmount(false).await?;
boot_guard.unmount(false).await?;
startos.unmount(false).await?;
root_guard.unmount(false).await?;
}
sync_boot_phase.complete();
finalize_phase.start();
Command::new("ln")
.arg("-rsf")
.arg(&path) .arg(&path)
.arg("/media/startos/config/current.rootfs") .invoke(ErrorKind::Grub)
.invoke(crate::ErrorKind::Filesystem)
.await?; .await?;
Command::new("sync").invoke(ErrorKind::Filesystem).await?;
finalize_phase.complete(); finalize_phase.complete();
progress.complete(); progress.complete();

View File

@@ -1,11 +1,10 @@
use std::pin::Pin; use std::pin::Pin;
use std::sync::Weak;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use axum::middleware::FromFn;
use futures::future::{BoxFuture, FusedFuture, abortable, pending}; use futures::future::{BoxFuture, FusedFuture, abortable, pending};
use futures::stream::{AbortHandle, Abortable, BoxStream}; use futures::stream::{AbortHandle, Abortable, BoxStream};
use futures::{Future, FutureExt, Stream, StreamExt}; use futures::{Future, FutureExt, Stream, StreamExt};
use rpc_toolkit::from_fn_blocking;
use tokio::sync::watch; use tokio::sync::watch;
use tokio::task::LocalSet; use tokio::task::LocalSet;
@@ -201,3 +200,26 @@ async fn test_cancellable() {
handle.cancel_and_wait().await; handle.cancel_and_wait().await;
assert!(weak.strong_count() == 0); assert!(weak.strong_count() == 0);
} }
#[pin_project::pin_project]
pub struct WeakFuture<Fut> {
rc: Weak<()>,
#[pin]
fut: Fut,
}
impl<Fut> WeakFuture<Fut> {
pub fn new(rc: Weak<()>, fut: Fut) -> Self {
Self { rc, fut }
}
}
impl<Fut: Future> Future for WeakFuture<Fut> {
type Output = Option<Fut::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
if this.rc.strong_count() > 0 {
this.fut.poll(cx).map(Some)
} else {
Poll::Ready(None)
}
}
}

View File

@@ -49,7 +49,7 @@ pub mod net;
pub mod rpc; pub mod rpc;
pub mod rpc_client; pub mod rpc_client;
pub mod serde; pub mod serde;
//pub mod squashfs; // pub mod squashfs;
pub mod sync; pub mod sync;
pub mod tui; pub mod tui;

View File

@@ -98,8 +98,7 @@ impl<W: Write> Visit<SquashfsSerializer<W>> for Superblock {
#[pin_project::pin_project] #[pin_project::pin_project]
pub struct MetadataBlocksWriter<W> { pub struct MetadataBlocksWriter<W> {
input: [u8; 8192], input: PartialBuffer<[u8; 8192]>,
size: usize,
size_addr: Option<u64>, size_addr: Option<u64>,
output: PartialBuffer<[u8; 8192]>, output: PartialBuffer<[u8; 8192]>,
output_flushed: usize, output_flushed: usize,
@@ -123,25 +122,29 @@ enum WriteState {
WritingSizeHeader(u16), WritingSizeHeader(u16),
WritingOutput(Box<Self>), WritingOutput(Box<Self>),
EncodingInput, EncodingInput,
FinishingCompression,
WritingFinalSizeHeader(u64, u64),
SeekingToEnd(u64), SeekingToEnd(u64),
} }
fn poll_seek_helper<W: AsyncSeek>( fn poll_seek_helper<W: AsyncSeek>(
writer: std::pin::Pin<&mut W>, mut writer: std::pin::Pin<&mut W>,
seek_state: &mut SeekState, seek_state: &mut SeekState,
cx: &mut std::task::Context<'_>, cx: &mut std::task::Context<'_>,
pos: u64, pos: u64,
) -> std::task::Poll<std::io::Result<u64>> { ) -> std::task::Poll<std::io::Result<u64>> {
match *seek_state { match *seek_state {
SeekState::Idle => { SeekState::Idle => {
writer.start_seek(std::io::SeekFrom::Start(pos))?; writer.as_mut().start_seek(std::io::SeekFrom::Start(pos))?;
*seek_state = SeekState::Seeking(pos); *seek_state = SeekState::Seeking(pos);
Poll::Pending match writer.as_mut().poll_complete(cx)? {
Poll::Ready(result) => {
*seek_state = SeekState::Idle;
Poll::Ready(Ok(result))
}
Poll::Pending => Poll::Pending,
}
} }
SeekState::Seeking(target) if target == pos => { SeekState::Seeking(target) if target == pos => {
let result = ready!(writer.poll_complete(cx))?; let result = ready!(writer.as_mut().poll_complete(cx))?;
*seek_state = SeekState::Idle; *seek_state = SeekState::Idle;
Poll::Ready(Ok(result)) Poll::Ready(Ok(result))
} }
@@ -151,35 +154,53 @@ fn poll_seek_helper<W: AsyncSeek>(
pos, pos,
old_target old_target
); );
writer.start_seek(std::io::SeekFrom::Start(pos))?; writer.as_mut().start_seek(std::io::SeekFrom::Start(pos))?;
*seek_state = SeekState::Seeking(pos); *seek_state = SeekState::Seeking(pos);
Poll::Pending match writer.as_mut().poll_complete(cx)? {
Poll::Ready(result) => {
*seek_state = SeekState::Idle;
Poll::Ready(Ok(result))
}
Poll::Pending => Poll::Pending,
}
} }
SeekState::GettingPosition => { SeekState::GettingPosition => {
tracing::warn!( tracing::warn!(
"poll_seek({}) called while getting stream position, canceling", "poll_seek({}) called while getting stream position, canceling",
pos pos
); );
writer.start_seek(std::io::SeekFrom::Start(pos))?; writer.as_mut().start_seek(std::io::SeekFrom::Start(pos))?;
*seek_state = SeekState::Seeking(pos); *seek_state = SeekState::Seeking(pos);
Poll::Pending match writer.as_mut().poll_complete(cx)? {
Poll::Ready(result) => {
*seek_state = SeekState::Idle;
Poll::Ready(Ok(result))
}
Poll::Pending => Poll::Pending,
}
} }
} }
} }
fn poll_stream_position_helper<W: AsyncSeek>( fn poll_stream_position_helper<W: AsyncSeek>(
writer: std::pin::Pin<&mut W>, mut writer: std::pin::Pin<&mut W>,
seek_state: &mut SeekState, seek_state: &mut SeekState,
cx: &mut std::task::Context<'_>, cx: &mut std::task::Context<'_>,
) -> std::task::Poll<std::io::Result<u64>> { ) -> std::task::Poll<std::io::Result<u64>> {
match *seek_state { match *seek_state {
SeekState::Idle => { SeekState::Idle => {
writer.start_seek(std::io::SeekFrom::Current(0))?; writer.as_mut().start_seek(std::io::SeekFrom::Current(0))?;
*seek_state = SeekState::GettingPosition; *seek_state = SeekState::GettingPosition;
Poll::Pending match writer.as_mut().poll_complete(cx)? {
Poll::Ready(result) => {
*seek_state = SeekState::Idle;
Poll::Ready(Ok(result))
}
Poll::Pending => Poll::Pending,
}
} }
SeekState::GettingPosition => { SeekState::GettingPosition => {
let result = ready!(writer.poll_complete(cx))?; let result = ready!(writer.as_mut().poll_complete(cx))?;
*seek_state = SeekState::Idle; *seek_state = SeekState::Idle;
Poll::Ready(Ok(result)) Poll::Ready(Ok(result))
} }
@@ -188,18 +209,22 @@ fn poll_stream_position_helper<W: AsyncSeek>(
"poll_stream_position called while seeking to {}, canceling", "poll_stream_position called while seeking to {}, canceling",
target target
); );
writer.start_seek(std::io::SeekFrom::Current(0))?; writer.as_mut().start_seek(std::io::SeekFrom::Current(0))?;
*seek_state = SeekState::GettingPosition; *seek_state = SeekState::GettingPosition;
Poll::Pending match writer.as_mut().poll_complete(cx)? {
Poll::Ready(result) => {
*seek_state = SeekState::Idle;
Poll::Ready(Ok(result))
}
Poll::Pending => Poll::Pending,
}
} }
} }
} }
impl<W: Write + Seek> Write for MetadataBlocksWriter<W> { impl<W: Write + Seek> Write for MetadataBlocksWriter<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let n = buf.len().min(self.input.len() - self.size); let n = self.input.copy_unwritten_from(&mut PartialBuffer::new(buf));
self.input[self.size..self.size + n].copy_from_slice(&buf[..n]);
self.size += n;
if n < buf.len() { if n < buf.len() {
self.flush()?; self.flush()?;
} }
@@ -207,9 +232,9 @@ impl<W: Write + Seek> Write for MetadataBlocksWriter<W> {
} }
fn flush(&mut self) -> std::io::Result<()> { fn flush(&mut self) -> std::io::Result<()> {
loop { loop {
match self.write_state { match &self.write_state {
WriteState::Idle => { WriteState::Idle => {
if self.size == 0 { if self.input.written().is_empty() {
return Ok(()); return Ok(());
} }
self.write_state = WriteState::WritingSizeHeader(0); self.write_state = WriteState::WritingSizeHeader(0);
@@ -218,12 +243,12 @@ impl<W: Write + Seek> Write for MetadataBlocksWriter<W> {
WriteState::WritingSizeHeader(size) => { WriteState::WritingSizeHeader(size) => {
let done = if let Some(size_addr) = self.size_addr { let done = if let Some(size_addr) = self.size_addr {
self.writer.seek(SeekFrom::Start(size_addr))?; self.writer.seek(SeekFrom::Start(size_addr))?;
Some(size_addr + size as u64) Some(size_addr + 2 + *size as u64)
} else { } else {
self.size_addr = Some(self.writer.stream_position()?); self.size_addr = Some(self.writer.stream_position()?);
None None
}; };
self.output.unwritten_mut()[..2].copy_from_slice(&u16::to_le_bytes(size)[..]); self.output.unwritten_mut()[..2].copy_from_slice(&u16::to_le_bytes(*size)[..]);
self.output.advance(2); self.output.advance(2);
self.write_state = self.write_state =
WriteState::WritingOutput(Box::new(if let Some(end) = done { WriteState::WritingOutput(Box::new(if let Some(end) = done {
@@ -242,80 +267,33 @@ impl<W: Write + Seek> Write for MetadataBlocksWriter<W> {
} else { } else {
self.output.reset(); self.output.reset();
self.output_flushed = 0; self.output_flushed = 0;
self.write_state = *next; self.write_state = *next.clone();
} }
} }
WriteState::EncodingInput => { WriteState::EncodingInput => {
let encoder = self.zstd.get_or_insert_with(|| ZstdEncoder::new(22)); let encoder = self.zstd.get_or_insert_with(|| ZstdEncoder::new(22));
let mut input = PartialBuffer::new(&self.input[..self.size]); encoder.encode(
while !self.output.unwritten().is_empty() && !input.unwritten().is_empty() { &mut PartialBuffer::new(&self.input.written()),
encoder.encode(&mut input, &mut self.output)?; &mut self.output,
} )?;
while !encoder.flush(&mut self.output)? {} let compressed = if !encoder.finish(&mut self.output)? {
while !encoder.finish(&mut self.output)? {} std::mem::swap(&mut self.output, &mut self.input);
if !self.output.unwritten().is_empty() { false
let mut input = } else {
PartialBuffer::new(&self.input[self.input_flushed..self.size]); true
encoder.encode(&mut input, &mut self.output)?; };
self.input_flushed += input.written().len(); self.zstd = None;
} self.input.reset();
self.write_state = WriteState::WritingOutput(Box::new()); self.write_state =
continue; WriteState::WritingOutput(Box::new(WriteState::WritingSizeHeader(
} self.output.written().len() as u16
| if compressed { 0 } else { 0x8000 },
WriteState::FinishingCompression => { )));
if !self.output.unwritten().is_empty() {
if self.zstd.as_mut().unwrap().finish(&mut self.output)? {
self.zstd = None;
}
}
if self.output.written().len() > self.output_flushed {
self.write_state = WriteState::WritingOutput;
continue;
}
if self.zstd.is_none() && self.output.written().len() == self.output_flushed {
self.output_flushed = 0;
self.output.reset();
let end_addr = self.writer.stream_position()?;
let size_addr = self.size_addr.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"size_addr not set when finishing compression",
)
})?;
self.write_state = WriteState::WritingFinalSizeHeader(size_addr, end_addr);
continue;
}
return Ok(());
}
WriteState::WritingFinalSizeHeader(size_addr, end_addr) => {
if self.output.written().len() > self.output_flushed {
let n = self
.writer
.write(&self.output.written()[self.output_flushed..])?;
self.output_flushed += n;
continue;
}
self.writer.seek(std::io::SeekFrom::Start(size_addr))?;
self.output.unwritten_mut()[..2]
.copy_from_slice(&((end_addr - size_addr - 2) as u16).to_le_bytes());
self.output.advance(2);
let n = self.writer.write(&self.output.written())?;
self.output_flushed = n;
if n == 2 {
self.output_flushed = 0;
self.output.reset();
self.write_state = WriteState::SeekingToEnd(end_addr);
}
continue;
} }
WriteState::SeekingToEnd(end_addr) => { WriteState::SeekingToEnd(end_addr) => {
self.writer.seek(std::io::SeekFrom::Start(end_addr))?; self.writer.seek(std::io::SeekFrom::Start(*end_addr))?;
self.input_flushed = 0;
self.size = 0;
self.size_addr = None; self.size_addr = None;
self.write_state = WriteState::Idle; self.write_state = WriteState::Idle;
return Ok(()); return Ok(());
@@ -332,11 +310,9 @@ impl<W: AsyncWrite + AsyncSeek> AsyncWrite for MetadataBlocksWriter<W> {
buf: &[u8], buf: &[u8],
) -> std::task::Poll<std::io::Result<usize>> { ) -> std::task::Poll<std::io::Result<usize>> {
let this = self.as_mut().project(); let this = self.as_mut().project();
let n = buf.len().min(this.input.len() - *this.size); let n = this.input.copy_unwritten_from(&mut PartialBuffer::new(buf));
this.input[*this.size..*this.size + n].copy_from_slice(&buf[..n]);
*this.size += n;
if n < buf.len() { if n < buf.len() {
ready!(self.poll_flush(cx)?); ready!(self.poll_flush(cx))?;
} }
Poll::Ready(Ok(n)) Poll::Ready(Ok(n))
} }
@@ -347,115 +323,76 @@ impl<W: AsyncWrite + AsyncSeek> AsyncWrite for MetadataBlocksWriter<W> {
) -> std::task::Poll<std::io::Result<()>> { ) -> std::task::Poll<std::io::Result<()>> {
loop { loop {
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
match *this.write_state { match this.write_state.clone() {
WriteState::Idle => { WriteState::Idle => {
if *this.size == 0 { if this.input.written().is_empty() {
return Poll::Ready(Ok(())); return Poll::Ready(Ok(()));
} }
if this.size_addr.is_none() { *this.write_state = WriteState::WritingSizeHeader(0);
}
WriteState::WritingSizeHeader(size) => {
let done = if let Some(size_addr) = *this.size_addr {
ready!(poll_seek_helper(
this.writer.as_mut(),
this.seek_state,
cx,
size_addr
))?;
Some(size_addr + 2 + size as u64)
} else {
let pos = ready!(poll_stream_position_helper( let pos = ready!(poll_stream_position_helper(
this.writer.as_mut(), this.writer.as_mut(),
this.seek_state, this.seek_state,
cx cx
))?; ))?;
*this.size_addr = Some(pos); *this.size_addr = Some(pos);
this.output.unwritten_mut()[..2].copy_from_slice(&[0; 2]); None
this.output.advance(2); };
} this.output.unwritten_mut()[..2]
*this.write_state = WriteState::WritingOutput; .copy_from_slice(&u16::to_le_bytes(size)[..]);
continue; this.output.advance(2);
} *this.write_state = WriteState::WritingOutput(Box::new(if let Some(end) = done {
WriteState::SeekingToEnd(end)
WriteState::WritingOutput => {
if this.output.written().len() > *this.output_flushed {
let n = ready!(
this.writer
.as_mut()
.poll_write(cx, &this.output.written()[*this.output_flushed..])
)?;
*this.output_flushed += n;
continue;
}
if this.output.written().len() == *this.output_flushed {
*this.output_flushed = 0;
this.output.reset();
}
if *this.input_flushed < *this.size {
if !this.output.unwritten().is_empty() {
let mut input =
PartialBuffer::new(&this.input[*this.input_flushed..*this.size]);
this.zstd
.get_or_insert_with(|| ZstdEncoder::new(22))
.encode(&mut input, this.output)?;
*this.input_flushed += input.written().len();
}
continue;
} else { } else {
if !this.output.unwritten().is_empty() { WriteState::EncodingInput
if this.zstd.as_mut().unwrap().finish(this.output)? { }));
*this.zstd = None;
}
continue;
}
if this.zstd.is_none()
&& this.output.written().len() == *this.output_flushed
{
*this.output_flushed = 0;
this.output.reset();
if let Some(size_addr) = *this.size_addr {
let end_addr = ready!(poll_stream_position_helper(
this.writer.as_mut(),
this.seek_state,
cx
))?;
*this.write_state =
WriteState::WritingFinalSizeHeader(size_addr, end_addr);
ready!(poll_seek_helper(
this.writer.as_mut(),
this.seek_state,
cx,
size_addr
))?;
this.output.unwritten_mut()[..2].copy_from_slice(
&((end_addr - size_addr - 2) as u16).to_le_bytes(),
);
this.output.advance(2);
continue;
}
}
}
return Poll::Ready(Ok(()));
} }
WriteState::WritingSizeHeader(_size_addr) => { WriteState::WritingOutput(next) => {
*this.write_state = WriteState::WritingOutput; if this.output.written().len() > *this.output_flushed {
continue; let n = ready!(this
.writer
.as_mut()
.poll_write(cx, &this.output.written()[*this.output_flushed..]))?;
*this.output_flushed += n;
} else {
this.output.reset();
*this.output_flushed = 0;
*this.write_state = *next;
}
} }
WriteState::EncodingInput => { WriteState::EncodingInput => {
*this.write_state = WriteState::WritingOutput; let encoder = this.zstd.get_or_insert_with(|| ZstdEncoder::new(22));
continue; encoder.encode(
} &mut PartialBuffer::new(this.input.written()),
this.output,
WriteState::FinishingCompression => { )?;
*this.write_state = WriteState::WritingOutput; let compressed = if !encoder.finish(this.output)? {
continue; std::mem::swap(this.output, this.input);
} false
} else {
WriteState::WritingFinalSizeHeader(_size_addr, end_addr) => { true
if this.output.written().len() > *this.output_flushed { };
let n = ready!( *this.zstd = None;
this.writer this.input.reset();
.as_mut() *this.write_state = WriteState::WritingOutput(Box::new(
.poll_write(cx, &this.output.written()[*this.output_flushed..]) WriteState::WritingSizeHeader(
)?; this.output.written().len() as u16
*this.output_flushed += n; | if compressed { 0 } else { 0x8000 },
continue; ),
} ));
*this.output_flushed = 0;
this.output.reset();
*this.write_state = WriteState::SeekingToEnd(end_addr);
continue;
} }
WriteState::SeekingToEnd(end_addr) => { WriteState::SeekingToEnd(end_addr) => {
@@ -466,8 +403,6 @@ impl<W: AsyncWrite + AsyncSeek> AsyncWrite for MetadataBlocksWriter<W> {
end_addr end_addr
))?; ))?;
*this.size_addr = None; *this.size_addr = None;
*this.input_flushed = 0;
*this.size = 0;
*this.write_state = WriteState::Idle; *this.write_state = WriteState::Idle;
return Poll::Ready(Ok(())); return Poll::Ready(Ok(()));
} }
@@ -486,11 +421,9 @@ impl<W: AsyncWrite + AsyncSeek> AsyncWrite for MetadataBlocksWriter<W> {
impl<W> MetadataBlocksWriter<W> { impl<W> MetadataBlocksWriter<W> {
pub fn new(writer: W) -> Self { pub fn new(writer: W) -> Self {
Self { Self {
input: [0; 8192], input: PartialBuffer::new([0; 8192]),
input_flushed: 0,
size: 0,
size_addr: None, size_addr: None,
output: PartialBuffer::new([0; 4096]), output: PartialBuffer::new([0; 8192]),
output_flushed: 0, output_flushed: 0,
zstd: None, zstd: None,
seek_state: SeekState::Idle, seek_state: SeekState::Idle,
@@ -507,11 +440,10 @@ use tokio::io::AsyncRead;
pub struct MetadataBlocksReader<R> { pub struct MetadataBlocksReader<R> {
#[pin] #[pin]
reader: R, reader: R,
size_buf: [u8; 2], size_buf: PartialBuffer<[u8; 2]>,
size_bytes_read: usize, compressed: PartialBuffer<[u8; 8192]>,
compressed: [u8; 8192],
compressed_size: usize, compressed_size: usize,
compressed_pos: usize, is_compressed: bool,
output: PartialBuffer<[u8; 8192]>, output: PartialBuffer<[u8; 8192]>,
output_pos: usize, output_pos: usize,
zstd: Option<ZstdDecoder>, zstd: Option<ZstdDecoder>,
@@ -531,11 +463,10 @@ impl<R> MetadataBlocksReader<R> {
pub fn new(reader: R) -> Self { pub fn new(reader: R) -> Self {
Self { Self {
reader, reader,
size_buf: [0; 2], size_buf: PartialBuffer::new([0; 2]),
size_bytes_read: 0, compressed: PartialBuffer::new([0; 8192]),
compressed: [0; 8192],
compressed_size: 0, compressed_size: 0,
compressed_pos: 0, is_compressed: false,
output: PartialBuffer::new([0; 8192]), output: PartialBuffer::new([0; 8192]),
output_pos: 0, output_pos: 0,
zstd: None, zstd: None,
@@ -551,11 +482,9 @@ impl<R: Read> Read for MetadataBlocksReader<R> {
loop { loop {
match self.state { match self.state {
ReadState::ReadingSize => { ReadState::ReadingSize => {
let n = self let n = self.reader.read(self.size_buf.unwritten_mut())?;
.reader
.read(&mut self.size_buf[self.size_bytes_read..])?;
if n == 0 { if n == 0 {
if self.size_bytes_read == 0 { if self.size_buf.written().is_empty() {
self.state = ReadState::Eof; self.state = ReadState::Eof;
return Ok(0); return Ok(0);
} else { } else {
@@ -566,56 +495,57 @@ impl<R: Read> Read for MetadataBlocksReader<R> {
} }
} }
self.size_bytes_read += n; self.size_buf.advance(n);
if self.size_bytes_read < 2 {
if self.size_buf.written().len() < 2 {
continue; continue;
} }
let size_header = u16::from_le_bytes(self.size_buf); let size_header = u16::from_le_bytes([
let is_compressed = (size_header & 0x8000) == 0; self.size_buf.written()[0],
let size = (size_header & 0x7FFF) as usize; self.size_buf.written()[1],
]);
self.is_compressed = (size_header & 0x8000) == 0;
self.compressed_size = (size_header & 0x7FFF) as usize;
if !is_compressed { if self.compressed_size == 0 || self.compressed_size > 8192 {
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData, std::io::ErrorKind::InvalidData,
"Uncompressed metadata blocks not supported", format!("Invalid metadata block size: {}", self.compressed_size),
)); ));
} }
if size == 0 || size > 8192 { self.compressed.reset();
return Err(std::io::Error::new( self.size_buf.reset();
std::io::ErrorKind::InvalidData,
format!("Invalid metadata block size: {}", size),
));
}
self.compressed_size = size;
self.compressed_pos = 0;
self.size_bytes_read = 0;
self.state = ReadState::ReadingData; self.state = ReadState::ReadingData;
continue; continue;
} }
ReadState::ReadingData => { ReadState::ReadingData => {
let n = self let n = self.reader.read(self.compressed.unwritten_mut())?;
.reader
.read(&mut self.compressed[self.compressed_pos..self.compressed_size])?;
if n == 0 { if n == 0 {
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof, std::io::ErrorKind::UnexpectedEof,
"Unexpected EOF reading compressed data", "Unexpected EOF reading data",
)); ));
} }
self.compressed_pos += n; self.compressed.advance(n);
if self.compressed_pos < self.compressed_size {
if !self.compressed.unwritten().is_empty() {
continue; continue;
} }
self.zstd = Some(ZstdDecoder::new());
self.output_pos = 0; self.output_pos = 0;
self.output.reset(); self.output.reset();
self.state = ReadState::Decompressing; if self.is_compressed {
self.zstd = Some(ZstdDecoder::new());
self.state = ReadState::Decompressing;
} else {
self.output
.copy_unwritten_from(&mut PartialBuffer::new(self.compressed.written()));
self.state = ReadState::Outputting;
}
continue; continue;
} }
@@ -625,7 +555,7 @@ impl<R: Read> Read for MetadataBlocksReader<R> {
continue; continue;
} }
let mut input = PartialBuffer::new(&self.compressed[..self.compressed_size]); let mut input = PartialBuffer::new(self.compressed.written());
let decoder = self.zstd.as_mut().unwrap(); let decoder = self.zstd.as_mut().unwrap();
if decoder.decode(&mut input, &mut self.output)? { if decoder.decode(&mut input, &mut self.output)? {
@@ -676,13 +606,13 @@ impl<R: AsyncRead> AsyncRead for MetadataBlocksReader<R> {
match *this.state { match *this.state {
ReadState::ReadingSize => { ReadState::ReadingSize => {
let mut read_buf = let mut read_buf = tokio::io::ReadBuf::new(this.size_buf.unwritten_mut());
tokio::io::ReadBuf::new(&mut this.size_buf[*this.size_bytes_read..]); let before = read_buf.filled().len();
ready!(this.reader.as_mut().poll_read(cx, &mut read_buf))?; ready!(this.reader.as_mut().poll_read(cx, &mut read_buf))?;
let n = read_buf.filled().len() - before;
let n = read_buf.filled().len();
if n == 0 { if n == 0 {
if *this.size_bytes_read == 0 { if this.size_buf.written().is_empty() {
*this.state = ReadState::Eof; *this.state = ReadState::Eof;
return Poll::Ready(Ok(())); return Poll::Ready(Ok(()));
} else { } else {
@@ -693,22 +623,16 @@ impl<R: AsyncRead> AsyncRead for MetadataBlocksReader<R> {
} }
} }
*this.size_bytes_read += n; this.size_buf.advance(n);
if *this.size_bytes_read < 2 {
if this.size_buf.written().len() < 2 {
continue; continue;
} }
let size_header = u16::from_le_bytes(*this.size_buf); let size_header = u16::from_le_bytes(*this.size_buf.written());
let is_compressed = (size_header & 0x8000) == 0; *this.is_compressed = (size_header & 0x8000) == 0;
let size = (size_header & 0x7FFF) as usize; let size = (size_header & 0x7FFF) as usize;
if !is_compressed {
return Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Uncompressed metadata blocks not supported",
)));
}
if size == 0 || size > 8192 { if size == 0 || size > 8192 {
return Poll::Ready(Err(std::io::Error::new( return Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidData, std::io::ErrorKind::InvalidData,
@@ -716,36 +640,42 @@ impl<R: AsyncRead> AsyncRead for MetadataBlocksReader<R> {
))); )));
} }
*this.compressed_size = size; this.compressed.reset();
*this.compressed_pos = 0; this.compressed.reserve(size);
*this.size_bytes_read = 0; this.size_buf.reset();
*this.state = ReadState::ReadingData; *this.state = ReadState::ReadingData;
continue; continue;
} }
ReadState::ReadingData => { ReadState::ReadingData => {
let mut read_buf = tokio::io::ReadBuf::new( let mut read_buf = tokio::io::ReadBuf::new(this.compressed.unwritten_mut());
&mut this.compressed[*this.compressed_pos..*this.compressed_size], let before = read_buf.filled().len();
);
ready!(this.reader.as_mut().poll_read(cx, &mut read_buf))?; ready!(this.reader.as_mut().poll_read(cx, &mut read_buf))?;
let n = read_buf.filled().len() - before;
let n = read_buf.filled().len();
if n == 0 { if n == 0 {
return Poll::Ready(Err(std::io::Error::new( return Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof, std::io::ErrorKind::UnexpectedEof,
"Unexpected EOF reading compressed data", "Unexpected EOF reading data",
))); )));
} }
*this.compressed_pos += n; this.compressed.advance(n);
if *this.compressed_pos < *this.compressed_size {
if !this.compressed.unwritten().is_empty() {
continue; continue;
} }
*this.zstd = Some(ZstdDecoder::new());
*this.output_pos = 0; *this.output_pos = 0;
this.output.reset(); this.output.reset();
*this.state = ReadState::Decompressing; if *this.is_compressed {
*this.zstd = Some(ZstdDecoder::new());
*this.state = ReadState::Decompressing;
} else {
this.output
.copy_unwritten_from(&mut PartialBuffer::new(this.compressed.written()));
*this.state = ReadState::Outputting;
}
continue; continue;
} }
@@ -755,7 +685,7 @@ impl<R: AsyncRead> AsyncRead for MetadataBlocksReader<R> {
continue; continue;
} }
let mut input = PartialBuffer::new(&this.compressed[..*this.compressed_size]); let mut input = PartialBuffer::new(this.compressed.written());
let decoder = this.zstd.as_mut().unwrap(); let decoder = this.zstd.as_mut().unwrap();
if decoder.decode(&mut input, this.output)? { if decoder.decode(&mut input, this.output)? {

View File

@@ -52,8 +52,9 @@ mod v0_4_0_alpha_9;
mod v0_4_0_alpha_10; mod v0_4_0_alpha_10;
mod v0_4_0_alpha_11; mod v0_4_0_alpha_11;
mod v0_4_0_alpha_12; mod v0_4_0_alpha_12;
mod v0_4_0_alpha_13;
pub type Current = v0_4_0_alpha_12::Version; // VERSION_BUMP pub type Current = v0_4_0_alpha_13::Version; // VERSION_BUMP
impl Current { impl Current {
#[instrument(skip(self, db))] #[instrument(skip(self, db))]
@@ -167,7 +168,8 @@ enum Version {
V0_4_0_alpha_9(Wrapper<v0_4_0_alpha_9::Version>), V0_4_0_alpha_9(Wrapper<v0_4_0_alpha_9::Version>),
V0_4_0_alpha_10(Wrapper<v0_4_0_alpha_10::Version>), V0_4_0_alpha_10(Wrapper<v0_4_0_alpha_10::Version>),
V0_4_0_alpha_11(Wrapper<v0_4_0_alpha_11::Version>), V0_4_0_alpha_11(Wrapper<v0_4_0_alpha_11::Version>),
V0_4_0_alpha_12(Wrapper<v0_4_0_alpha_12::Version>), // VERSION_BUMP V0_4_0_alpha_12(Wrapper<v0_4_0_alpha_12::Version>),
V0_4_0_alpha_13(Wrapper<v0_4_0_alpha_13::Version>), // VERSION_BUMP
Other(exver::Version), Other(exver::Version),
} }
@@ -222,7 +224,8 @@ impl Version {
Self::V0_4_0_alpha_9(v) => DynVersion(Box::new(v.0)), Self::V0_4_0_alpha_9(v) => DynVersion(Box::new(v.0)),
Self::V0_4_0_alpha_10(v) => DynVersion(Box::new(v.0)), Self::V0_4_0_alpha_10(v) => DynVersion(Box::new(v.0)),
Self::V0_4_0_alpha_11(v) => DynVersion(Box::new(v.0)), Self::V0_4_0_alpha_11(v) => DynVersion(Box::new(v.0)),
Self::V0_4_0_alpha_12(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP Self::V0_4_0_alpha_12(v) => DynVersion(Box::new(v.0)),
Self::V0_4_0_alpha_13(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
Self::Other(v) => { Self::Other(v) => {
return Err(Error::new( return Err(Error::new(
eyre!("unknown version {v}"), eyre!("unknown version {v}"),
@@ -269,7 +272,8 @@ impl Version {
Version::V0_4_0_alpha_9(Wrapper(x)) => x.semver(), Version::V0_4_0_alpha_9(Wrapper(x)) => x.semver(),
Version::V0_4_0_alpha_10(Wrapper(x)) => x.semver(), Version::V0_4_0_alpha_10(Wrapper(x)) => x.semver(),
Version::V0_4_0_alpha_11(Wrapper(x)) => x.semver(), Version::V0_4_0_alpha_11(Wrapper(x)) => x.semver(),
Version::V0_4_0_alpha_12(Wrapper(x)) => x.semver(), // VERSION_BUMP Version::V0_4_0_alpha_12(Wrapper(x)) => x.semver(),
Version::V0_4_0_alpha_13(Wrapper(x)) => x.semver(), // VERSION_BUMP
Version::Other(x) => x.clone(), Version::Other(x) => x.clone(),
} }
} }

View File

@@ -0,0 +1,37 @@
use exver::{PreReleaseSegment, VersionRange};
use super::v0_3_5::V0_3_0_COMPAT;
use super::{VersionT, v0_4_0_alpha_12};
use crate::prelude::*;
lazy_static::lazy_static! {
static ref V0_4_0_alpha_13: exver::Version = exver::Version::new(
[0, 4, 0],
[PreReleaseSegment::String("alpha".into()), 13.into()]
);
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Version;
impl VersionT for Version {
type Previous = v0_4_0_alpha_12::Version;
type PreUpRes = ();
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
Ok(())
}
fn semver(self) -> exver::Version {
V0_4_0_alpha_13.clone()
}
fn compat(self) -> &'static VersionRange {
&V0_3_0_COMPAT
}
#[instrument(skip_all)]
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
Ok(Value::Null)
}
fn down(self, _db: &mut Value) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -122,7 +122,6 @@ 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 echo "fs.inotify.max_user_watches=1048576" > /etc/sysctl.d/97-startos.conf
locale-gen en_US.UTF-8
dpkg-reconfigure --frontend noninteractive locales dpkg-reconfigure --frontend noninteractive locales
if ! getent group | grep '^startos:'; then if ! getent group | grep '^startos:'; then

View File

@@ -63,7 +63,7 @@ find . -type f -not -path "./DEBIAN/*" -exec md5sum {} \; | sort -k 2 | sed 's/\
cd ../.. cd ../..
cd dpkg-workdir cd dpkg-workdir
dpkg-deb --root-owner-group -b $BASENAME dpkg-deb --root-owner-group -Zzstd -b $BASENAME
mkdir -p ../results mkdir -p ../results
mv $BASENAME.deb ../results/$BASENAME.deb mv $BASENAME.deb ../results/$BASENAME.deb
rm -rf $BASENAME rm -rf $BASENAME

View File

@@ -7,6 +7,11 @@ BASEDIR="$(pwd -P)"
SUITE=trixie SUITE=trixie
USE_TTY=
if tty -s; then
USE_TTY="-it"
fi
dockerfile_hash=$(sha256sum ${BASEDIR}/image-recipe/Dockerfile | head -c 7) dockerfile_hash=$(sha256sum ${BASEDIR}/image-recipe/Dockerfile | head -c 7)
docker_img_name="startos_build:${SUITE}-${dockerfile_hash}" docker_img_name="startos_build:${SUITE}-${dockerfile_hash}"

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
export type ForgetGatewayParams = { gateway: GatewayId }

View File

@@ -1,7 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
export type NetworkInterfaceSetPublicParams = {
gateway: GatewayId
public: boolean | null
}

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
export type RenameGatewayParams = { id: GatewayId; name: string }

View File

@@ -1,4 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GatewayId } from "./GatewayId"
export type UnsetPublicParams = { gateway: GatewayId }

View File

@@ -76,7 +76,6 @@ export { EventId } from "./EventId"
export { ExportActionParams } from "./ExportActionParams" export { ExportActionParams } from "./ExportActionParams"
export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams" export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams"
export { FileType } from "./FileType" export { FileType } from "./FileType"
export { ForgetGatewayParams } from "./ForgetGatewayParams"
export { FullIndex } from "./FullIndex" export { FullIndex } from "./FullIndex"
export { FullProgress } from "./FullProgress" export { FullProgress } from "./FullProgress"
export { GatewayId } from "./GatewayId" export { GatewayId } from "./GatewayId"
@@ -143,7 +142,6 @@ export { NamedProgress } from "./NamedProgress"
export { NetInfo } from "./NetInfo" export { NetInfo } from "./NetInfo"
export { NetworkInfo } from "./NetworkInfo" export { NetworkInfo } from "./NetworkInfo"
export { NetworkInterfaceInfo } from "./NetworkInterfaceInfo" export { NetworkInterfaceInfo } from "./NetworkInterfaceInfo"
export { NetworkInterfaceSetPublicParams } from "./NetworkInterfaceSetPublicParams"
export { NetworkInterfaceType } from "./NetworkInterfaceType" export { NetworkInterfaceType } from "./NetworkInterfaceType"
export { OnionHostname } from "./OnionHostname" export { OnionHostname } from "./OnionHostname"
export { OsIndex } from "./OsIndex" export { OsIndex } from "./OsIndex"
@@ -175,7 +173,6 @@ export { RemovePackageFromCategoryParams } from "./RemovePackageFromCategoryPara
export { RemovePackageParams } from "./RemovePackageParams" export { RemovePackageParams } from "./RemovePackageParams"
export { RemoveTunnelParams } from "./RemoveTunnelParams" export { RemoveTunnelParams } from "./RemoveTunnelParams"
export { RemoveVersionParams } from "./RemoveVersionParams" export { RemoveVersionParams } from "./RemoveVersionParams"
export { RenameGatewayParams } from "./RenameGatewayParams"
export { ReplayId } from "./ReplayId" export { ReplayId } from "./ReplayId"
export { RequestCommitment } from "./RequestCommitment" export { RequestCommitment } from "./RequestCommitment"
export { RunActionParams } from "./RunActionParams" export { RunActionParams } from "./RunActionParams"
@@ -211,7 +208,6 @@ export { TaskSeverity } from "./TaskSeverity"
export { TaskTrigger } from "./TaskTrigger" export { TaskTrigger } from "./TaskTrigger"
export { Task } from "./Task" export { Task } from "./Task"
export { TestSmtpParams } from "./TestSmtpParams" export { TestSmtpParams } from "./TestSmtpParams"
export { UnsetPublicParams } from "./UnsetPublicParams"
export { UpdatingState } from "./UpdatingState" export { UpdatingState } from "./UpdatingState"
export { VerifyCifsParams } from "./VerifyCifsParams" export { VerifyCifsParams } from "./VerifyCifsParams"
export { VersionSignerParams } from "./VersionSignerParams" export { VersionSignerParams } from "./VersionSignerParams"

View File

@@ -61,7 +61,7 @@ import {
} from "../../base/lib/inits" } from "../../base/lib/inits"
import { DropGenerator } from "../../base/lib/util/Drop" import { DropGenerator } from "../../base/lib/util/Drop"
export const OSVersion = testTypeVersion("0.4.0-alpha.12") export const OSVersion = testTypeVersion("0.4.0-alpha.13")
// prettier-ignore // prettier-ignore
type AnyNeverCond<T extends any[], Then, Else> = type AnyNeverCond<T extends any[], Then, Else> =

View File

@@ -77,10 +77,14 @@ export class CommandController<
if (exec.runAsInit) { if (exec.runAsInit) {
childProcess = await subcontainer!.launch(commands, { childProcess = await subcontainer!.launch(commands, {
env: exec.env, env: exec.env,
user: exec.user,
cwd: exec.cwd,
}) })
} else { } else {
childProcess = await subcontainer!.spawn(commands, { childProcess = await subcontainer!.spawn(commands, {
env: exec.env, env: exec.env,
user: exec.user,
cwd: exec.cwd,
stdio: exec.onStdout || exec.onStderr ? "pipe" : "inherit", stdio: exec.onStdout || exec.onStderr ? "pipe" : "inherit",
}) })
} }

View File

@@ -1,12 +1,12 @@
{ {
"name": "@start9labs/start-sdk", "name": "@start9labs/start-sdk",
"version": "0.4.0-beta.42", "version": "0.4.0-beta.43",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@start9labs/start-sdk", "name": "@start9labs/start-sdk",
"version": "0.4.0-beta.42", "version": "0.4.0-beta.43",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@iarna/toml": "^3.0.0", "@iarna/toml": "^3.0.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@start9labs/start-sdk", "name": "@start9labs/start-sdk",
"version": "0.4.0-beta.42", "version": "0.4.0-beta.43",
"description": "Software development kit to facilitate packaging services for StartOS", "description": "Software development kit to facilitate packaging services for StartOS",
"main": "./package/lib/index.js", "main": "./package/lib/index.js",
"types": "./package/lib/index.d.ts", "types": "./package/lib/index.d.ts",

4
web/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "startos-ui", "name": "startos-ui",
"version": "0.4.0-alpha.12", "version": "0.4.0-alpha.13",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "startos-ui", "name": "startos-ui",
"version": "0.4.0-alpha.12", "version": "0.4.0-alpha.13",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@angular/animations": "^20.3.0", "@angular/animations": "^20.3.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "startos-ui", "name": "startos-ui",
"version": "0.4.0-alpha.12", "version": "0.4.0-alpha.13",
"author": "Start9 Labs, Inc", "author": "Start9 Labs, Inc",
"homepage": "https://start9.com/", "homepage": "https://start9.com/",
"license": "MIT", "license": "MIT",

View File

@@ -59,7 +59,9 @@ export class AppComponent {
await this.api.reboot() await this.api.reboot()
this.dialogs this.dialogs
.open( .open(
'Please wait 1-2 minutes, then refresh this page to access the StartOS setup wizard.', window.location.host === 'localhost'
? 'Please wait 1-2 minutes for your server to restart'
: 'Please wait 1-2 minutes, then refresh this page to access the StartOS setup wizard.',
{ {
label: 'Rebooting', label: 'Rebooting',
size: 's', size: 's',

View File

@@ -110,7 +110,7 @@ export namespace Mock {
squashfs: { squashfs: {
aarch64: { aarch64: {
publishedAt: '2025-04-21T20:58:48.140749883Z', publishedAt: '2025-04-21T20:58:48.140749883Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.12/startos-0.4.0-alpha.12-33ae46f~dev_aarch64.squashfs', url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.13/startos-0.4.0-alpha.13-33ae46f~dev_aarch64.squashfs',
commitment: { commitment: {
hash: '4elBFVkd/r8hNadKmKtLIs42CoPltMvKe2z3LRqkphk=', hash: '4elBFVkd/r8hNadKmKtLIs42CoPltMvKe2z3LRqkphk=',
size: 1343500288, size: 1343500288,
@@ -122,7 +122,7 @@ export namespace Mock {
}, },
'aarch64-nonfree': { 'aarch64-nonfree': {
publishedAt: '2025-04-21T21:07:00.249285116Z', publishedAt: '2025-04-21T21:07:00.249285116Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.12/startos-0.4.0-alpha.12-33ae46f~dev_aarch64-nonfree.squashfs', url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.13/startos-0.4.0-alpha.13-33ae46f~dev_aarch64-nonfree.squashfs',
commitment: { commitment: {
hash: 'MrCEi4jxbmPS7zAiGk/JSKlMsiuKqQy6RbYOxlGHOIQ=', hash: 'MrCEi4jxbmPS7zAiGk/JSKlMsiuKqQy6RbYOxlGHOIQ=',
size: 1653075968, size: 1653075968,
@@ -134,7 +134,7 @@ export namespace Mock {
}, },
raspberrypi: { raspberrypi: {
publishedAt: '2025-04-21T21:16:12.933319237Z', publishedAt: '2025-04-21T21:16:12.933319237Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.12/startos-0.4.0-alpha.12-33ae46f~dev_raspberrypi.squashfs', url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.13/startos-0.4.0-alpha.13-33ae46f~dev_raspberrypi.squashfs',
commitment: { commitment: {
hash: '/XTVQRCqY3RK544PgitlKu7UplXjkmzWoXUh2E4HCw0=', hash: '/XTVQRCqY3RK544PgitlKu7UplXjkmzWoXUh2E4HCw0=',
size: 1490731008, size: 1490731008,
@@ -146,7 +146,7 @@ export namespace Mock {
}, },
x86_64: { x86_64: {
publishedAt: '2025-04-21T21:14:20.246908903Z', publishedAt: '2025-04-21T21:14:20.246908903Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.12/startos-0.4.0-alpha.12-33ae46f~dev_x86_64.squashfs', url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.13/startos-0.4.0-alpha.13-33ae46f~dev_x86_64.squashfs',
commitment: { commitment: {
hash: '/6romKTVQGSaOU7FqSZdw0kFyd7P+NBSYNwM3q7Fe44=', hash: '/6romKTVQGSaOU7FqSZdw0kFyd7P+NBSYNwM3q7Fe44=',
size: 1411657728, size: 1411657728,
@@ -158,7 +158,7 @@ export namespace Mock {
}, },
'x86_64-nonfree': { 'x86_64-nonfree': {
publishedAt: '2025-04-21T21:15:17.955265284Z', publishedAt: '2025-04-21T21:15:17.955265284Z',
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.12/startos-0.4.0-alpha.12-33ae46f~dev_x86_64-nonfree.squashfs', url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.13/startos-0.4.0-alpha.13-33ae46f~dev_x86_64-nonfree.squashfs',
commitment: { commitment: {
hash: 'HCRq9sr/0t85pMdrEgNBeM4x11zVKHszGnD1GDyZbSE=', hash: 'HCRq9sr/0t85pMdrEgNBeM4x11zVKHszGnD1GDyZbSE=',
size: 1731035136, size: 1731035136,
@@ -385,7 +385,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org', docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!', releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6', osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.42', sdkVersion: '0.4.0-beta.43',
gitHash: 'fakehash', gitHash: 'fakehash',
icon: BTC_ICON, icon: BTC_ICON,
sourceVersion: null, sourceVersion: null,
@@ -420,7 +420,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org', docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!', releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6', osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.42', sdkVersion: '0.4.0-beta.43',
gitHash: 'fakehash', gitHash: 'fakehash',
icon: BTC_ICON, icon: BTC_ICON,
sourceVersion: null, sourceVersion: null,
@@ -465,7 +465,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org', docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!', releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6', osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.42', sdkVersion: '0.4.0-beta.43',
gitHash: 'fakehash', gitHash: 'fakehash',
icon: BTC_ICON, icon: BTC_ICON,
sourceVersion: null, sourceVersion: null,
@@ -500,7 +500,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org', docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!', releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6', osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.42', sdkVersion: '0.4.0-beta.43',
gitHash: 'fakehash', gitHash: 'fakehash',
icon: BTC_ICON, icon: BTC_ICON,
sourceVersion: null, sourceVersion: null,
@@ -547,7 +547,7 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/', docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release to 0.17.5', releaseNotes: 'Upstream release to 0.17.5',
osVersion: '0.3.6', osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.42', sdkVersion: '0.4.0-beta.43',
gitHash: 'fakehash', gitHash: 'fakehash',
icon: LND_ICON, icon: LND_ICON,
sourceVersion: null, sourceVersion: null,
@@ -595,7 +595,7 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/', docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release to 0.17.4', releaseNotes: 'Upstream release to 0.17.4',
osVersion: '0.3.6', osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.42', sdkVersion: '0.4.0-beta.43',
gitHash: 'fakehash', gitHash: 'fakehash',
icon: LND_ICON, icon: LND_ICON,
sourceVersion: null, sourceVersion: null,
@@ -647,7 +647,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org', docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!', releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6', osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.42', sdkVersion: '0.4.0-beta.43',
gitHash: 'fakehash', gitHash: 'fakehash',
icon: BTC_ICON, icon: BTC_ICON,
sourceVersion: null, sourceVersion: null,
@@ -682,7 +682,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org', docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!', releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6', osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.42', sdkVersion: '0.4.0-beta.43',
gitHash: 'fakehash', gitHash: 'fakehash',
icon: BTC_ICON, icon: BTC_ICON,
sourceVersion: null, sourceVersion: null,
@@ -727,7 +727,7 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/', docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release and minor fixes.', releaseNotes: 'Upstream release and minor fixes.',
osVersion: '0.3.6', osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.42', sdkVersion: '0.4.0-beta.43',
gitHash: 'fakehash', gitHash: 'fakehash',
icon: LND_ICON, icon: LND_ICON,
sourceVersion: null, sourceVersion: null,
@@ -775,7 +775,7 @@ export namespace Mock {
marketingSite: '', marketingSite: '',
releaseNotes: 'Upstream release and minor fixes.', releaseNotes: 'Upstream release and minor fixes.',
osVersion: '0.3.6', osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.42', sdkVersion: '0.4.0-beta.43',
gitHash: 'fakehash', gitHash: 'fakehash',
icon: PROXY_ICON, icon: PROXY_ICON,
sourceVersion: null, sourceVersion: null,