Compare commits

..

49 Commits

Author SHA1 Message Date
Aiden McClelland
d6a84e41e6 delete useless effectful test 2025-11-07 02:33:17 -07:00
Aiden McClelland
a7bce7cfcc fix rsync tests 2025-11-07 02:09:21 -07:00
Aiden McClelland
ab2c52b72c fix tests 2025-11-07 01:58:44 -07:00
Aiden McClelland
05e73be7b2 fix tests 2025-11-07 00:16:48 -07:00
Aiden McClelland
ae07469442 try to fix build 2025-11-06 21:35:25 -07:00
Aiden McClelland
c51a5fcff1 fix build 2025-11-06 20:54:46 -07:00
Aiden McClelland
c04e061505 fix build 2025-11-06 20:33:34 -07:00
Aiden McClelland
d7cb16f1b5 fix build 2025-11-06 20:24:23 -07:00
Aiden McClelland
50f1d314b8 fix build 2025-11-06 19:57:46 -07:00
Aiden McClelland
c6174b8315 Merge branch 'feature/start-tunnel' of github.com:Start9Labs/start-os into feature/start-tunnel 2025-11-06 18:05:30 -07:00
Aiden McClelland
ac1085ff9b fix raspi build 2025-11-06 17:53:56 -07:00
Alex Inkin
66cb9a93b8 fix: address comments (#3044)
* fix: address comments

* fix unread notification mocks

* fix row click for notification

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
2025-11-06 14:17:57 -07:00
Aiden McClelland
515d37147b build fixes 2025-11-06 10:46:14 -07:00
Matt Hill
acdade473c handle rpc errors 2025-11-05 20:35:59 -07:00
Aiden McClelland
18b659653d better validation 2025-11-05 17:05:56 -07:00
Aiden McClelland
7e888b825c fixes 2025-11-05 14:51:11 -07:00
Matt Hill
2c05e6129c better regex for subnet 2025-11-05 12:48:56 -07:00
waterplea
e0995a63ca fix: address comments 2025-11-05 11:10:33 +04:00
Aiden McClelland
056a9ff9b6 tui tweaks 2025-11-04 18:11:19 -07:00
Aiden McClelland
01400cb9ce better tui 2025-11-04 16:57:57 -07:00
Alex Inkin
58d9f5ef6a refactor: break big components apart and address todos (#3043)
* refactor: break big components apart and address todos

* starttunnel readme, fix pf mocks, fix adding tor domain in startos

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
2025-11-04 11:15:25 -07:00
Matt Hill
69d0391d12 predict next subnet and ip, use wan ips, and form validation 2025-11-01 15:51:25 -06:00
Aiden McClelland
304f8c3a97 misc fixes 2025-10-31 18:38:02 -06:00
Aiden McClelland
6c11102c09 bump sdk 2025-10-31 15:42:52 -06:00
Aiden McClelland
9e714f34dd Merge branch 'next/major' of github.com:Start9Labs/start-os into feature/start-tunnel 2025-10-31 15:42:05 -06:00
Aiden McClelland
5852bcadf8 simplify iptables rules 2025-10-31 15:41:00 -06:00
Aiden McClelland
5ae9a555ce wip: separate port forward controller into parts 2025-10-31 12:21:02 -06:00
Aiden McClelland
afc69b13a0 api fixes 2025-10-31 11:42:56 -06:00
Aiden McClelland
1a46dde11b Merge branches 'feature/start-tunnel' and 'feature/start-tunnel' of github.com:Start9Labs/start-os into feature/start-tunnel 2025-10-30 16:17:58 -06:00
Aiden McClelland
0e84970ae5 fix tests 2025-10-30 16:17:56 -06:00
Matt Hill
3dcdca18a3 endpoint for getting config 2025-10-30 14:33:21 -06:00
Matt Hill
6ff329c897 more providers 2025-10-30 14:03:21 -06:00
Aiden McClelland
02637e81e3 Merge branch 'feature/start-tunnel' of github.com:Start9Labs/start-os into feature/start-tunnel 2025-10-30 13:38:12 -06:00
Aiden McClelland
df27c0c629 fixes 2025-10-30 13:37:52 -06:00
Matt Hill
c631311e96 minor change toi viewing addresses and fix build 2025-10-30 13:36:49 -06:00
Aiden McClelland
5340c421e1 build and formatter update 2025-10-29 17:26:45 -06:00
Aiden McClelland
0bd79b28b4 finish api 2025-10-29 13:52:57 -06:00
Aiden McClelland
5580ff6f01 refactor complete 2025-10-29 11:17:49 -06:00
Matt Hill
124ed625d9 fix editing records and patch mocks 2025-10-26 09:02:03 -06:00
Matt Hill
33b5f189e2 FE patchdb, mocks, and most endpoints 2025-10-25 13:21:22 -06:00
Aiden McClelland
82a3a435f5 wip: tls refactor 2025-10-24 09:25:30 -06:00
Matt Hill
2056d4def1 web server WIP 2025-10-21 16:01:14 -06:00
Aiden McClelland
40b00bae75 wip 2025-10-20 18:05:57 -06:00
Aiden McClelland
716bf920f5 non-free-firmware on trixie 2025-10-12 19:04:03 -06:00
Aiden McClelland
cd88977a78 Merge branch 'next/major' of github.com:Start9Labs/start-os into feature/start-tunnel 2025-10-12 10:36:12 -06:00
Aiden McClelland
a630ef9a54 fixes for trixie and tor 2025-10-12 08:49:11 -06:00
Aiden McClelland
98f31d4891 wip: start-tunnel 2025-09-27 03:05:03 -06:00
Aiden McClelland
24521e3cac improved debuggability 2025-09-24 22:28:24 -06:00
Aiden McClelland
ad5d3ad01d fix live-build resolv.conf 2025-09-24 19:20:27 -06:00
55 changed files with 698 additions and 819 deletions

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
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,conntrack ./build/os-compat/run-compat.sh ./dpkg-build.sh
PROJECT=start-tunnel PLATFORM=$(ARCH) REQUIRES=debian DEPENDS=wireguard-tools,iptables ./build/os-compat/run-compat.sh ./dpkg-build.sh
$(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}'))
@echo "Paste the following command into the shell of your StartOS server:"
@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/upgrade ./$(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/use-img ./$(BASENAME).squashfs'"'"'\n", $$3 }'
update: $(STARTOS_TARGETS)
@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-boot')
$(call cp,results/$(BASENAME).squashfs,/media/startos/images/next.rootfs)
$(call ssh,'sudo CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/upgrade /media/startos/images/next.rootfs')
$(call ssh,'sudo CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/use-img /media/startos/images/next.rootfs')
emulate-reflash: $(STARTOS_TARGETS)
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi

View File

@@ -6,14 +6,6 @@ You can think of StartTunnel as "virtual router in the cloud"
Use it for private, remote access, to self-hosted services running on a personal server, or to expose self-hosted services to the public Internet without revealing the host server's IP address.
## 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
1. Rent a low cost VPS. For most use cases, the cheapest option should be enough.
@@ -28,11 +20,15 @@ Use it for private, remote access, to self-hosted services running on a personal
1. Install StartTunnel:
```sh
TMP_DIR=$(mktemp -d) && (cd $TMP_DIR && wget https://github.com/Start9Labs/start-os/releases/download/v0.4.0-alpha.12/start-tunnel-0.4.0-alpha.12-68f401b_$(uname -m).deb && apt-get install -y ./start-tunnel-0.4.0-alpha.12-68f401b_$(uname -m).deb) && rm -rf $TMP_DIR && systemctl start start-tunneld && echo "Installation Succeeded"
```
@TODO
5. [Initialize the web interface](#web-interface) (recommended)
## 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.
## CLI
@@ -56,8 +52,8 @@ If you choose to enable the web interface (recommended in most cases), StartTunn
1. Select whether to autogenerate a self-signed certificate or provide your own certificate and key. If you choose to autogenerate, you will be asked to list all IP addresses and domains for which to sign the certificate. For example, if you intend to access your StartTunnel web UI at a domain, include the domain in the list.
1. You will receive a success message with 3 pieces of information:
1. You will receive a success message that the webserver is running at the chosen IP:port, as well as your SSL certificate and an autogenerated UI password.
- <https://IP:port>: the URL where you can reach your personal web interface.
- Password: an autogenerated password for your interface. If you lose/forget it, you can reset using the CLI.
- Root Certificate Authority: the Root CA of your StartTunnel instance. If not already, trust it in your browser or system keychain.
1. If not already, trust the certificate in your system keychain and/or browser.
1. If you lose/forget your password, you can reset it using the CLI.

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,82 +0,0 @@
#!/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...'

61
build/lib/scripts/use-img Executable file
View File

@@ -0,0 +1,61 @@
#!/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": {
"name": "@start9labs/start-sdk",
"version": "0.4.0-beta.43",
"version": "0.4.0-beta.42",
"license": "MIT",
"dependencies": {
"@iarna/toml": "^3.0.0",

View File

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

35
core/Cargo.lock generated
View File

@@ -3458,7 +3458,7 @@ dependencies = [
"lazy_async_pool",
"models",
"pin-project",
"rpc-toolkit 0.3.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=master)",
"rpc-toolkit",
"serde",
"serde_json",
"tokio",
@@ -4835,7 +4835,7 @@ dependencies = [
"rand 0.9.2",
"regex",
"reqwest",
"rpc-toolkit 0.3.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?branch=master)",
"rpc-toolkit",
"rustls 0.23.35",
"serde",
"serde_json",
@@ -6744,34 +6744,6 @@ dependencies = [
"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]]
name = "rsa"
version = "0.9.8"
@@ -7916,6 +7888,7 @@ dependencies = [
"async-compression",
"async-stream",
"async-trait",
"aws-lc-sys",
"axum 0.8.6",
"backtrace-on-stack-overflow",
"barrage",
@@ -8008,7 +7981,7 @@ dependencies = [
"reqwest",
"reqwest_cookie_store",
"rpassword",
"rpc-toolkit 0.3.2 (git+https://github.com/Start9Labs/rpc-toolkit.git?rev=068db90)",
"rpc-toolkit",
"rust-argon2",
"safelog",
"semver",

View File

@@ -2,8 +2,6 @@
cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea
shopt -s expand_aliases
@@ -20,20 +18,15 @@ if [ "$ARCH" = "arm64" ]; then
ARCH="aarch64"
fi
RUST_ARCH="$ARCH"
if [ "$ARCH" = "riscv64" ]; then
RUST_ARCH="riscv64gc"
fi
if [ -z "${KERNEL_NAME:-}" ]; then
KERNEL_NAME=$(uname -s)
fi
if [ -z "${TARGET:-}" ]; then
if [ "$KERNEL_NAME" = "Linux" ]; then
TARGET="$RUST_ARCH-unknown-linux-musl"
TARGET="$ARCH-unknown-linux-musl"
elif [ "$KERNEL_NAME" = "Darwin" ]; then
TARGET="$RUST_ARCH-apple-darwin"
TARGET="$ARCH-apple-darwin"
else
>&2 echo "unknown kernel $KERNEL_NAME"
exit 1
@@ -60,7 +53,4 @@ fi
echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\""
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/release/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
cross build --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features $FEATURE_ARGS --locked --bin start-cli --target=$TARGET

View File

@@ -2,8 +2,6 @@
cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea
shopt -s expand_aliases
@@ -35,7 +33,4 @@ fi
echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\""
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/release/containerbox" | awk '{ print $3 }')" != "$UID" ]; then
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
fi
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

View File

@@ -2,8 +2,6 @@
cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea
shopt -s expand_aliases
@@ -35,7 +33,4 @@ fi
echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\""
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/release/registrybox" | awk '{ print $3 }')" != "$UID" ]; then
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
fi
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

View File

@@ -2,8 +2,6 @@
cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea
shopt -s expand_aliases
@@ -35,7 +33,4 @@ fi
echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\""
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/release/startbox" | awk '{ print $3 }')" != "$UID" ]; then
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
fi
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

View File

@@ -2,8 +2,6 @@
cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea
shopt -s expand_aliases
@@ -33,7 +31,4 @@ if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
fi
echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\""
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
cross test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --no-default-features --features test,$FEATURES --locked 'export_bindings_'

View File

@@ -2,8 +2,6 @@
cd "$(dirname "${BASH_SOURCE[0]}")"
source ./builder-alias.sh
set -ea
shopt -s expand_aliases
@@ -35,7 +33,4 @@ fi
echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\""
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/release/tunnelbox" | awk '{ print $3 }')" != "$UID" ]; then
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
fi
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

View File

@@ -1,8 +1,3 @@
#!/bin/bash
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'
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'

View File

@@ -93,6 +93,7 @@ async-compression = { version = "0.4.32", features = [
] }
async-stream = "0.3.5"
async-trait = "0.1.74"
aws-lc-sys = { version = "0.32", features = ["bindgen"] }
axum = { version = "0.8.4", features = ["ws"] }
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
barrage = "0.2.3"
@@ -222,7 +223,7 @@ regex = "1.10.2"
reqwest = { version = "0.12.4", features = ["json", "socks", "stream"] }
reqwest_cookie_store = "0.8.0"
rpassword = "7.2.0"
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", rev = "068db90" }
rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch = "master" }
rust-argon2 = "2.0.0"
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"] }
@@ -251,7 +252,7 @@ termion = "4.0.5"
textwrap = "0.16.1"
thiserror = "2.0.12"
tokio = { version = "1.38.1", features = ["full"] }
tokio-rustls = "0.26.4"
tokio-rustls = "0.26.0"
tokio-stream = { version = "0.1.14", features = ["io-util", "net", "sync"] }
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
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::util::logger::LOGGER;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum WebserverListener {
Http,
Https(SocketAddr),

View File

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

View File

@@ -280,9 +280,6 @@ pub async fn list(os: &OsPartitionInfo) -> Result<Vec<DiskInfo>, Error> {
.try_fold(
BTreeMap::<PathBuf, DiskIndex>::new(),
|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()) {
let (disk_path, part_path) = if let Some(end) = PARTITION_REGEX.find(disk_path) {
(

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
use std::any::Any;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::fmt;
use std::future::Future;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV6};
use std::sync::{Arc, Weak};
@@ -131,6 +130,7 @@ async fn list_interfaces(
}
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)]
struct NetworkInterfaceSetPublicParams {
gateway: GatewayId,
public: Option<bool>,
@@ -147,6 +147,7 @@ async fn set_public(
}
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)]
struct UnsetPublicParams {
gateway: GatewayId,
}
@@ -162,6 +163,7 @@ async fn unset_public(
}
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)]
struct ForgetGatewayParams {
gateway: GatewayId,
}
@@ -174,6 +176,7 @@ async fn forget_iface(
}
#[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)]
#[ts(export)]
struct RenameGatewayParams {
id: GatewayId,
name: InternedString,
@@ -401,12 +404,6 @@ async fn watcher(
) {
loop {
let res: Result<(), Error> = async {
Command::new("systemctl")
.arg("start")
.arg("NetworkManager")
.invoke(ErrorKind::Network)
.await?;
let connection = Connection::system().await?;
let netman_proxy = NetworkManagerProxy::new(&connection).await?;
@@ -438,60 +435,49 @@ async fn watcher(
loop {
until
.run(async {
loop {
let devices = netman_proxy.all_devices().await?;
if devices.is_empty() {
tracing::warn!(
"NetworkManager returned no devices. Trying again..."
);
tokio::time::sleep(Duration::from_secs(1)).await;
let devices = netman_proxy.all_devices().await?;
let mut ifaces = BTreeSet::new();
let mut jobs = Vec::new();
for device in devices {
use futures::future::Either;
let device_proxy =
device::DeviceProxy::new(&connection, device.clone()).await?;
let iface = InternedString::intern(device_proxy.ip_interface().await?);
if iface.is_empty() {
continue;
}
let mut ifaces = BTreeSet::new();
let mut jobs = Vec::new();
for device in devices {
use futures::future::Either;
let device_proxy =
device::DeviceProxy::new(&connection, device.clone()).await?;
let iface =
InternedString::intern(device_proxy.ip_interface().await?);
if iface.is_empty() {
continue;
}
let iface: GatewayId = iface.into();
if watch_activation.peek(|a| a.contains_key(&iface)) {
jobs.push(Either::Left(watch_activated(
&connection,
device_proxy.clone(),
iface.clone(),
&watch_activation,
)));
}
jobs.push(Either::Right(watch_ip(
let iface: GatewayId = iface.into();
if watch_activation.peek(|a| a.contains_key(&iface)) {
jobs.push(Either::Left(watch_activated(
&connection,
device_proxy.clone(),
iface.clone(),
&watch_ip_info,
&watch_activation,
)));
ifaces.insert(iface);
}
watch_ip_info.send_if_modified(|m| {
let mut changed = false;
for (iface, info) in OrdMapIterMut::from(m) {
if !ifaces.contains(iface) {
info.ip_info = None;
changed = true;
}
}
changed
});
futures::future::try_join_all(jobs).await?;
break;
jobs.push(Either::Right(watch_ip(
&connection,
device_proxy.clone(),
iface.clone(),
&watch_ip_info,
)));
ifaces.insert(iface);
}
watch_ip_info.send_if_modified(|m| {
let mut changed = false;
for (iface, info) in OrdMapIterMut::from(m) {
if !ifaces.contains(iface) {
info.ip_info = None;
changed = true;
}
}
changed
});
futures::future::try_join_all(jobs).await?;
Ok::<_, Error>(())
})
.await?;
@@ -1552,14 +1538,6 @@ pub struct NetworkInterfaceListenerAcceptMetadata<B: Bind> {
pub inner: <B::Accept as Accept>::Metadata,
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>
where
<B::Accept as Accept>::Metadata: Clone,
@@ -1636,39 +1614,3 @@ where
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,
SubjectKeyIdentifier,
};
use openssl::x509::{X509, X509Builder, X509NameBuilder, X509Ref};
use openssl::x509::{X509, X509Builder, X509NameBuilder};
use openssl::*;
use patch_db::HasModel;
use serde::{Deserialize, Serialize};
@@ -48,17 +48,6 @@ 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)]
#[model = "Model<Self>"]
#[serde(rename_all = "camelCase")]
@@ -94,8 +83,30 @@ impl Model<CertStore> {
.map(|m| m.de())
.transpose()?
{
if should_use_cert(&cert_data.certs.ed25519)?
&& should_use_cert(&cert_data.certs.nistp256)?
if cert_data
.certs
.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 {
root: self.as_root_cert().de()?.0,

View File

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

View File

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

View File

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

View File

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

View File

@@ -150,39 +150,31 @@ impl ExecParams {
cmd.env(k, v);
}
if let Some((uid, gid)) =
if let Some(uid) = user.as_deref().and_then(|u| u.parse::<u32>().ok()) {
Some((uid, uid))
} else if let Some(user) = user {
let passwd = std::fs::read_to_string("/etc/passwd")
.with_ctx(|_| (ErrorKind::Filesystem, "read /etc/passwd"));
Some(if passwd.is_err() && user == "root" {
(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 {
None
}
{
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();
if let Some(uid) = user.as_deref().and_then(|u| u.parse::<u32>().ok()) {
cmd.uid(uid);
cmd.gid(gid);
}
} else if let Some(user) = user {
let passwd = std::fs::read_to_string("/etc/passwd")
.with_ctx(|_| (ErrorKind::Filesystem, "read /etc/passwd"));
if passwd.is_err() && user == "root" {
cmd.uid(0);
cmd.gid(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"))?;
cmd.uid(uid);
cmd.gid(gid);
}
};
if let Some(workdir) = workdir {
cmd.current_dir(workdir);
} else {

View File

@@ -725,8 +725,6 @@ pub struct AttachParams {
name: Option<InternedString>,
#[ts(type = "string | null")]
image_id: Option<ImageId>,
#[ts(type = "string | null")]
user: Option<InternedString>,
}
pub async fn attach(
ctx: RpcContext,
@@ -740,7 +738,6 @@ pub async fn attach(
subcontainer,
image_id,
name,
user,
}: AttachParams,
) -> Result<Guid, Error> {
let (container_id, subcontainer_id, image_id, workdir, root_command) = {
@@ -817,26 +814,9 @@ pub async fn attach(
.join("etc")
.join("passwd");
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 root_command = get_passwd_root_command(passwd).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());
let workdir = attach_workdir(&image_id, &root_dir).await?;
if subcontainer_ids.len() > 1 {
let subcontainer_ids = subcontainer_ids
@@ -869,7 +849,6 @@ pub async fn attach(
pty_size: Option<TermSize>,
image_id: ImageId,
workdir: Option<String>,
user: Option<InternedString>,
root_command: &RootCommand,
) -> Result<(), Error> {
use axum::extract::ws::Message;
@@ -892,10 +871,6 @@ pub async fn attach(
.with_extension("env"),
);
if let Some(user) = user {
cmd.arg("--user").arg(&*user);
}
if let Some(workdir) = workdir {
cmd.arg("--workdir").arg(workdir);
}
@@ -1057,7 +1032,6 @@ pub async fn attach(
pty_size,
image_id,
workdir,
user,
&root_command,
)
.await
@@ -1077,46 +1051,19 @@ pub async fn attach(
Ok(guid)
}
#[derive(Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
pub struct ListSubcontainersParams {
pub id: PackageId,
async fn attach_workdir(image_id: &ImageId, root_dir: &Path) -> Result<Option<String>, Error> {
let path_str = root_dir.join("media/startos/images/");
let mut subcontainer_json =
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()))
}
#[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 fn get_passwd_root_command(etc_passwd_path: PathBuf) -> RootCommand {
async {
let mut file = tokio::fs::File::open(etc_passwd_path).await?;
@@ -1127,8 +1074,8 @@ async fn get_passwd_command(etc_passwd_path: PathBuf, user: &str) -> RootCommand
for line in contents.split('\n') {
let line_information = line.split(':').collect::<Vec<_>>();
if let (Some(&u), Some(shell)) = (line_information.first(), line_information.last())
&& u == user
if let (Some(&"root"), Some(shell)) =
(line_information.first(), line_information.last())
{
return Ok(shell.to_string());
}
@@ -1159,8 +1106,6 @@ pub struct CliAttachParams {
#[arg(long, short)]
name: Option<InternedString>,
#[arg(long, short)]
user: Option<InternedString>,
#[arg(long, short)]
image_id: Option<ImageId>,
}
#[instrument[skip_all]]
@@ -1202,7 +1147,6 @@ pub async fn cli_attach(
"subcontainer": params.subcontainer,
"imageId": params.image_id,
"name": params.name,
"user": params.user,
}),
)
.await?,

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
use std::collections::BTreeMap;
use std::env::consts::ARCH;
use std::path::Path;
use std::time::Duration;
@@ -417,7 +416,9 @@ async fn do_update(
prune_phase.complete();
download_phase.start();
let path = Path::new("/media/startos/images/next.squashfs");
let path = Path::new("/media/startos/images")
.join(hex::encode(&asset.commitment.hash[..16]))
.with_extension("rootfs");
let mut dst = AtomicFile::new(&path, None::<&Path>)
.await
.with_kind(ErrorKind::Filesystem)?;
@@ -444,16 +445,75 @@ async fn do_update(
.arg("-d")
.arg("/")
.arg(&path)
.arg("/usr/lib/startos/scripts/upgrade")
.arg("boot")
.invoke(crate::ErrorKind::Filesystem)
.await?;
let checksum = hex::encode(&asset.commitment.hash[..16]);
Command::new("/usr/lib/startos/scripts/upgrade")
.env("CHECKSUM", &checksum)
.invoke(ErrorKind::Grub)
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")
.arg(root_guard.path())
.arg("update-grub2")
.invoke(ErrorKind::Grub)
.await?;
if let Some(efivarfs) = efivarfs {
efivarfs.unmount(false).await?;
}
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();

View File

@@ -1,10 +1,11 @@
use std::pin::Pin;
use std::sync::Weak;
use std::task::{Context, Poll};
use axum::middleware::FromFn;
use futures::future::{BoxFuture, FusedFuture, abortable, pending};
use futures::stream::{AbortHandle, Abortable, BoxStream};
use futures::{Future, FutureExt, Stream, StreamExt};
use rpc_toolkit::from_fn_blocking;
use tokio::sync::watch;
use tokio::task::LocalSet;
@@ -200,26 +201,3 @@ async fn test_cancellable() {
handle.cancel_and_wait().await;
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_client;
pub mod serde;
// pub mod squashfs;
//pub mod squashfs;
pub mod sync;
pub mod tui;

View File

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

View File

@@ -122,6 +122,7 @@ ln -sf /usr/lib/startos/scripts/wireguard-vps-proxy-setup /usr/bin/wireguard-vps
echo "fs.inotify.max_user_watches=1048576" > /etc/sysctl.d/97-startos.conf
locale-gen en_US.UTF-8
dpkg-reconfigure --frontend noninteractive locales
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 dpkg-workdir
dpkg-deb --root-owner-group -Zzstd -b $BASENAME
dpkg-deb --root-owner-group -b $BASENAME
mkdir -p ../results
mv $BASENAME.deb ../results/$BASENAME.deb
rm -rf $BASENAME

View File

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

View File

@@ -0,0 +1,4 @@
// 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

@@ -0,0 +1,7 @@
// 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

@@ -0,0 +1,4 @@
// 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

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

View File

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

View File

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

View File

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

View File

@@ -59,9 +59,7 @@ export class AppComponent {
await this.api.reboot()
this.dialogs
.open(
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.',
'Please wait 1-2 minutes, then refresh this page to access the StartOS setup wizard.',
{
label: 'Rebooting',
size: 's',

View File

@@ -56,7 +56,7 @@ export function getIp({ clients, range }: MappedSubnet) {
const net = IpNet.parse(range)
const last = net.broadcast()
for (let ip = net.add(1); ip.cmp(last) === -1; ip = ip.add(1)) {
for (let ip = net.add(1); ip.cmp(last) === -1; ip.add(1)) {
if (!clients[ip.address]) {
return ip.address
}

View File

@@ -228,27 +228,24 @@ export class PublicDomainService {
private gatewayAndAuthoritySpec() {
const data = this.data()!
const gateways = data.gateways.filter(
({ ipInfo: { deviceType } }) =>
deviceType !== 'loopback' && deviceType !== 'bridge',
)
return {
gateway: ISB.Value.dynamicSelect(() => ({
name: this.i18n.transform('Gateway'),
description: this.i18n.transform(
'Select a gateway to use for this domain.',
),
values: gateways.reduce<Record<string, string>>(
values: data.gateways.reduce<Record<string, string>>(
(obj, gateway) => ({
...obj,
[gateway.id]: gateway.name || gateway.ipInfo.name,
[gateway.id]: gateway.name || gateway.ipInfo!.name,
}),
{},
),
default: '',
disabled: gateways
.filter(g => !g.ipInfo.wanIp || utils.CGNAT.contains(g.ipInfo.wanIp))
disabled: data.gateways
.filter(
g => !g.ipInfo!.wanIp || utils.CGNAT.contains(g.ipInfo!.wanIp),
)
.map(g => g.id),
})),
authority: ISB.Value.select({

View File

@@ -385,7 +385,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.43',
sdkVersion: '0.4.0-beta.42',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -420,7 +420,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.43',
sdkVersion: '0.4.0-beta.42',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -465,7 +465,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.43',
sdkVersion: '0.4.0-beta.42',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -500,7 +500,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.43',
sdkVersion: '0.4.0-beta.42',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -547,7 +547,7 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release to 0.17.5',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.43',
sdkVersion: '0.4.0-beta.42',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
@@ -595,7 +595,7 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release to 0.17.4',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.43',
sdkVersion: '0.4.0-beta.42',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
@@ -647,7 +647,7 @@ export namespace Mock {
docsUrl: 'https://bitcoin.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.43',
sdkVersion: '0.4.0-beta.42',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -682,7 +682,7 @@ export namespace Mock {
docsUrl: 'https://bitcoinknots.org',
releaseNotes: 'Even better support for Bitcoin and wallets!',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.43',
sdkVersion: '0.4.0-beta.42',
gitHash: 'fakehash',
icon: BTC_ICON,
sourceVersion: null,
@@ -727,7 +727,7 @@ export namespace Mock {
docsUrl: 'https://lightning.engineering/',
releaseNotes: 'Upstream release and minor fixes.',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.43',
sdkVersion: '0.4.0-beta.42',
gitHash: 'fakehash',
icon: LND_ICON,
sourceVersion: null,
@@ -775,7 +775,7 @@ export namespace Mock {
marketingSite: '',
releaseNotes: 'Upstream release and minor fixes.',
osVersion: '0.3.6',
sdkVersion: '0.4.0-beta.43',
sdkVersion: '0.4.0-beta.42',
gitHash: 'fakehash',
icon: PROXY_ICON,
sourceVersion: null,