mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
Compare commits
25 Commits
v0.4.0-alp
...
v0.4.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
015ff02d71 | ||
|
|
10bfaf5415 | ||
|
|
e3e0b85e0c | ||
|
|
ad0632892e | ||
|
|
f26791ba39 | ||
|
|
2fbaaebf44 | ||
|
|
edb916338c | ||
|
|
f7e947d37d | ||
|
|
a9e3d1ed75 | ||
|
|
ce97827c42 | ||
|
|
3efec07338 | ||
|
|
68f401bfa3 | ||
|
|
1ea525feaa | ||
|
|
57c4a7527e | ||
|
|
5aa9c045e1 | ||
|
|
6f1900f3bb | ||
|
|
bc62de795e | ||
|
|
c62ca4b183 | ||
|
|
876e5bc683 | ||
|
|
b99f3b73cd | ||
|
|
7eecf29449 | ||
|
|
1d331d7810 | ||
|
|
68414678d8 | ||
|
|
2f6b9dac26 | ||
|
|
d1812d875b |
100
.github/workflows/start-tunnel.yaml
vendored
Normal file
100
.github/workflows/start-tunnel.yaml
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
name: Start-Tunnel
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
environment:
|
||||||
|
type: choice
|
||||||
|
description: Environment
|
||||||
|
options:
|
||||||
|
- NONE
|
||||||
|
- dev
|
||||||
|
- unstable
|
||||||
|
- dev-unstable
|
||||||
|
runner:
|
||||||
|
type: choice
|
||||||
|
description: Runner
|
||||||
|
options:
|
||||||
|
- standard
|
||||||
|
- fast
|
||||||
|
arch:
|
||||||
|
type: choice
|
||||||
|
description: Architecture
|
||||||
|
options:
|
||||||
|
- ALL
|
||||||
|
- x86_64
|
||||||
|
- aarch64
|
||||||
|
- riscv64
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODEJS_VERSION: "24.11.0"
|
||||||
|
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
compile:
|
||||||
|
name: Compile Base Binaries
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
arch: >-
|
||||||
|
${{
|
||||||
|
fromJson('{
|
||||||
|
"x86_64": ["x86_64"],
|
||||||
|
"aarch64": ["aarch64"],
|
||||||
|
"riscv64": ["riscv64"],
|
||||||
|
"ALL": ["x86_64", "aarch64", "riscv64"]
|
||||||
|
}')[github.event.inputs.platform || 'ALL']
|
||||||
|
}}
|
||||||
|
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||||
|
steps:
|
||||||
|
- name: Cleaning up unnecessary files
|
||||||
|
run: |
|
||||||
|
sudo apt-get remove --purge -y google-chrome-stable firefox mono-devel
|
||||||
|
sudo apt-get autoremove -y
|
||||||
|
sudo apt-get clean
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
sudo mount -t tmpfs tmpfs .
|
||||||
|
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODEJS_VERSION }}
|
||||||
|
|
||||||
|
- name: Set up docker QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Configure sccache
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
|
||||||
|
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||||
|
|
||||||
|
- name: Make
|
||||||
|
run: make tunnel-deb
|
||||||
|
env:
|
||||||
|
PLATFORM: ${{ matrix.arch }}
|
||||||
|
SCCACHE_GHA_ENABLED: on
|
||||||
|
SCCACHE_GHA_VERSION: 0
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: start-tunnel_${{ matrix.arch }}.deb
|
||||||
|
path: start-tunnel-*_${{ matrix.arch }}.deb
|
||||||
17
.github/workflows/startos-iso.yaml
vendored
17
.github/workflows/startos-iso.yaml
vendored
@@ -28,6 +28,7 @@ on:
|
|||||||
- aarch64
|
- aarch64
|
||||||
- aarch64-nonfree
|
- aarch64-nonfree
|
||||||
- raspberrypi
|
- raspberrypi
|
||||||
|
- riscv64
|
||||||
deploy:
|
deploy:
|
||||||
type: choice
|
type: choice
|
||||||
description: Deploy
|
description: Deploy
|
||||||
@@ -45,7 +46,7 @@ on:
|
|||||||
- next/*
|
- next/*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODEJS_VERSION: "22.17.1"
|
NODEJS_VERSION: "24.11.0"
|
||||||
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -62,11 +63,17 @@ jobs:
|
|||||||
"aarch64": ["aarch64"],
|
"aarch64": ["aarch64"],
|
||||||
"aarch64-nonfree": ["aarch64"],
|
"aarch64-nonfree": ["aarch64"],
|
||||||
"raspberrypi": ["aarch64"],
|
"raspberrypi": ["aarch64"],
|
||||||
|
"riscv64": ["riscv64"],
|
||||||
"ALL": ["x86_64", "aarch64"]
|
"ALL": ["x86_64", "aarch64"]
|
||||||
}')[github.event.inputs.platform || 'ALL']
|
}')[github.event.inputs.platform || 'ALL']
|
||||||
}}
|
}}
|
||||||
runs-on: ${{ fromJson('["ubuntu-22.04", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
runs-on: ${{ fromJson('["ubuntu-latest", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Cleaning up unnecessary files
|
||||||
|
run: |
|
||||||
|
sudo apt-get remove --purge -y google-chrome-stable firefox mono-devel
|
||||||
|
sudo apt-get autoremove -y
|
||||||
|
sudo apt-get clean
|
||||||
- run: |
|
- run: |
|
||||||
sudo mount -t tmpfs tmpfs .
|
sudo mount -t tmpfs tmpfs .
|
||||||
if: ${{ github.event.inputs.runner == 'fast' }}
|
if: ${{ github.event.inputs.runner == 'fast' }}
|
||||||
@@ -132,13 +139,14 @@ jobs:
|
|||||||
${{
|
${{
|
||||||
fromJson(
|
fromJson(
|
||||||
format(
|
format(
|
||||||
'["ubuntu-22.04", "{0}"]',
|
'["ubuntu-latest", "{0}"]',
|
||||||
fromJson('{
|
fromJson('{
|
||||||
"x86_64": "buildjet-8vcpu-ubuntu-2204",
|
"x86_64": "buildjet-8vcpu-ubuntu-2204",
|
||||||
"x86_64-nonfree": "buildjet-8vcpu-ubuntu-2204",
|
"x86_64-nonfree": "buildjet-8vcpu-ubuntu-2204",
|
||||||
"aarch64": "buildjet-8vcpu-ubuntu-2204-arm",
|
"aarch64": "buildjet-8vcpu-ubuntu-2204-arm",
|
||||||
"aarch64-nonfree": "buildjet-8vcpu-ubuntu-2204-arm",
|
"aarch64-nonfree": "buildjet-8vcpu-ubuntu-2204-arm",
|
||||||
"raspberrypi": "buildjet-8vcpu-ubuntu-2204-arm",
|
"raspberrypi": "buildjet-8vcpu-ubuntu-2204-arm",
|
||||||
|
"riscv64": "buildjet-8vcpu-ubuntu-2204",
|
||||||
}')[matrix.platform]
|
}')[matrix.platform]
|
||||||
)
|
)
|
||||||
)[github.event.inputs.runner == 'fast']
|
)[github.event.inputs.runner == 'fast']
|
||||||
@@ -152,6 +160,7 @@ jobs:
|
|||||||
"aarch64": "aarch64",
|
"aarch64": "aarch64",
|
||||||
"aarch64-nonfree": "aarch64",
|
"aarch64-nonfree": "aarch64",
|
||||||
"raspberrypi": "aarch64",
|
"raspberrypi": "aarch64",
|
||||||
|
"riscv64": "riscv64",
|
||||||
}')[matrix.platform]
|
}')[matrix.platform]
|
||||||
}}
|
}}
|
||||||
steps:
|
steps:
|
||||||
@@ -263,7 +272,7 @@ jobs:
|
|||||||
index:
|
index:
|
||||||
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
|
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
|
||||||
needs: [image]
|
needs: [image]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- run: >-
|
- run: >-
|
||||||
curl "https://${{
|
curl "https://${{
|
||||||
|
|||||||
4
.github/workflows/test.yaml
vendored
4
.github/workflows/test.yaml
vendored
@@ -11,13 +11,13 @@ on:
|
|||||||
- next/*
|
- next/*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODEJS_VERSION: "22.17.1"
|
NODEJS_VERSION: "24.11.0"
|
||||||
ENVIRONMENT: dev-unstable
|
ENVIRONMENT: dev-unstable
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Run Automated Tests
|
name: Run Automated Tests
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ docker buildx create --use
|
|||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # proceed with default installation
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # proceed with default installation
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
|
||||||
source ~/.bashrc
|
source ~/.bashrc
|
||||||
nvm install 22
|
nvm install 24
|
||||||
nvm use 22
|
nvm use 24
|
||||||
nvm alias default 22 # this prevents your machine from reverting back to another version
|
nvm alias default 24 # this prevents your machine from reverting back to another version
|
||||||
```
|
```
|
||||||
|
|
||||||
## Cloning the repository
|
## Cloning the repository
|
||||||
|
|||||||
137
Makefile
137
Makefile
@@ -5,15 +5,17 @@ PLATFORM_FILE := $(shell ./check-platform.sh)
|
|||||||
ENVIRONMENT_FILE := $(shell ./check-environment.sh)
|
ENVIRONMENT_FILE := $(shell ./check-environment.sh)
|
||||||
GIT_HASH_FILE := $(shell ./check-git-hash.sh)
|
GIT_HASH_FILE := $(shell ./check-git-hash.sh)
|
||||||
VERSION_FILE := $(shell ./check-version.sh)
|
VERSION_FILE := $(shell ./check-version.sh)
|
||||||
BASENAME := $(shell ./basename.sh)
|
BASENAME := $(shell PROJECT=startos ./basename.sh)
|
||||||
PLATFORM := $(shell if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)
|
PLATFORM := $(shell if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)
|
||||||
ARCH := $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo aarch64; else echo $(PLATFORM) | sed 's/-nonfree$$//g'; fi)
|
ARCH := $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo aarch64; else echo $(PLATFORM) | sed 's/-nonfree$$//g'; fi)
|
||||||
|
RUST_ARCH := $(shell if [ "$(ARCH)" = "riscv64" ]; then echo riscv64gc; else echo $(ARCH); fi)
|
||||||
|
REGISTRY_BASENAME := $(shell PROJECT=start-registry PLATFORM=$(ARCH) ./basename.sh)
|
||||||
|
TUNNEL_BASENAME := $(shell PROJECT=start-tunnel PLATFORM=$(ARCH) ./basename.sh)
|
||||||
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
||||||
WEB_UIS := web/dist/raw/ui/index.html web/dist/raw/setup-wizard/index.html web/dist/raw/install-wizard/index.html
|
WEB_UIS := web/dist/raw/ui/index.html web/dist/raw/setup-wizard/index.html web/dist/raw/install-wizard/index.html
|
||||||
COMPRESSED_WEB_UIS := web/dist/static/ui/index.html web/dist/static/setup-wizard/index.html web/dist/static/install-wizard/index.html
|
COMPRESSED_WEB_UIS := web/dist/static/ui/index.html web/dist/static/setup-wizard/index.html web/dist/static/install-wizard/index.html
|
||||||
FIRMWARE_ROMS := ./firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json)
|
FIRMWARE_ROMS := ./firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json)
|
||||||
BUILD_SRC := $(call ls-files, build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS)
|
BUILD_SRC := $(call ls-files, build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS)
|
||||||
DEBIAN_SRC := $(call ls-files, debian/)
|
|
||||||
IMAGE_RECIPE_SRC := $(call ls-files, image-recipe/)
|
IMAGE_RECIPE_SRC := $(call ls-files, image-recipe/)
|
||||||
STARTD_SRC := core/startos/startd.service $(BUILD_SRC)
|
STARTD_SRC := core/startos/startd.service $(BUILD_SRC)
|
||||||
CORE_SRC := $(call ls-files, core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE)
|
CORE_SRC := $(call ls-files, core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE)
|
||||||
@@ -21,19 +23,23 @@ WEB_SHARED_SRC := $(call ls-files, web/projects/shared) $(call ls-files, web/pro
|
|||||||
WEB_UI_SRC := $(call ls-files, web/projects/ui)
|
WEB_UI_SRC := $(call ls-files, web/projects/ui)
|
||||||
WEB_SETUP_WIZARD_SRC := $(call ls-files, web/projects/setup-wizard)
|
WEB_SETUP_WIZARD_SRC := $(call ls-files, web/projects/setup-wizard)
|
||||||
WEB_INSTALL_WIZARD_SRC := $(call ls-files, web/projects/install-wizard)
|
WEB_INSTALL_WIZARD_SRC := $(call ls-files, web/projects/install-wizard)
|
||||||
|
WEB_START_TUNNEL_SRC := $(call ls-files, web/projects/start-tunnel)
|
||||||
PATCH_DB_CLIENT_SRC := $(shell git ls-files --recurse-submodules patch-db/client)
|
PATCH_DB_CLIENT_SRC := $(shell git ls-files --recurse-submodules patch-db/client)
|
||||||
GZIP_BIN := $(shell which pigz || which gzip)
|
GZIP_BIN := $(shell which pigz || which gzip)
|
||||||
TAR_BIN := $(shell which gtar || which tar)
|
TAR_BIN := $(shell which gtar || which tar)
|
||||||
COMPILED_TARGETS := core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox core/target/$(ARCH)-unknown-linux-musl/release/containerbox container-runtime/rootfs.$(ARCH).squashfs
|
COMPILED_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox core/target/$(RUST_ARCH)-unknown-linux-musl/release/containerbox container-runtime/rootfs.$(ARCH).squashfs
|
||||||
ALL_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs $(PLATFORM_FILE) \
|
STARTOS_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs $(PLATFORM_FILE) \
|
||||||
$(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then \
|
$(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then \
|
||||||
echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; \
|
echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; \
|
||||||
fi) \
|
fi) \
|
||||||
$(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then \
|
$(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then \
|
||||||
echo cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console; \
|
echo cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph; \
|
||||||
echo cargo-deps/$(ARCH)-unknown-linux-musl/release/flamegraph; \
|
fi') \
|
||||||
|
$(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)console($$|-) ]]; then \
|
||||||
|
echo cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console; \
|
||||||
fi')
|
fi')
|
||||||
REBUILD_TYPES = 1
|
REGISTRY_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox core/startos/start-registryd.service
|
||||||
|
TUNNEL_TARGETS := core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox core/startos/start-tunneld.service
|
||||||
|
|
||||||
ifeq ($(REMOTE),)
|
ifeq ($(REMOTE),)
|
||||||
mkdir = mkdir -p $1
|
mkdir = mkdir -p $1
|
||||||
@@ -56,12 +62,12 @@ endif
|
|||||||
|
|
||||||
.DELETE_ON_ERROR:
|
.DELETE_ON_ERROR:
|
||||||
|
|
||||||
.PHONY: all metadata install clean format cli uis ui reflash deb $(IMAGE_TYPE) squashfs wormhole wormhole-deb test test-core test-sdk test-container-runtime registry
|
.PHONY: all metadata install clean format cli uis ui reflash deb $(IMAGE_TYPE) squashfs wormhole wormhole-deb test test-core test-sdk test-container-runtime registry install-registry tunnel install-tunnel ts-bindings
|
||||||
|
|
||||||
all: $(ALL_TARGETS)
|
all: $(STARTOS_TARGETS)
|
||||||
|
|
||||||
touch:
|
touch:
|
||||||
touch $(ALL_TARGETS)
|
touch $(STARTOS_TARGETS)
|
||||||
|
|
||||||
metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
||||||
|
|
||||||
@@ -109,40 +115,74 @@ test-container-runtime: container-runtime/node_modules/.package-lock.json $(call
|
|||||||
cli:
|
cli:
|
||||||
./core/install-cli.sh
|
./core/install-cli.sh
|
||||||
|
|
||||||
registry:
|
registry: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox
|
||||||
./core/build-registrybox.sh
|
|
||||||
|
|
||||||
tunnel:
|
install-registry: $(REGISTRY_TARGETS)
|
||||||
./core/build-tunnelbox.sh
|
$(call mkdir,$(DESTDIR)/usr/bin)
|
||||||
|
$(call cp,core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox,$(DESTDIR)/usr/bin/start-registrybox)
|
||||||
|
$(call ln,/usr/bin/start-registrybox,$(DESTDIR)/usr/bin/start-registryd)
|
||||||
|
$(call ln,/usr/bin/start-registrybox,$(DESTDIR)/usr/bin/start-registry)
|
||||||
|
|
||||||
|
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
||||||
|
$(call cp,core/startos/start-registryd.service,$(DESTDIR)/lib/systemd/system/start-registryd.service)
|
||||||
|
|
||||||
|
core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/registrybox: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
|
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-registrybox.sh
|
||||||
|
|
||||||
|
tunnel: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox
|
||||||
|
|
||||||
|
install-tunnel: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox core/startos/start-tunneld.service
|
||||||
|
$(call mkdir,$(DESTDIR)/usr/bin)
|
||||||
|
$(call cp,core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox,$(DESTDIR)/usr/bin/start-tunnelbox)
|
||||||
|
$(call ln,/usr/bin/start-tunnelbox,$(DESTDIR)/usr/bin/start-tunneld)
|
||||||
|
$(call ln,/usr/bin/start-tunnelbox,$(DESTDIR)/usr/bin/start-tunnel)
|
||||||
|
|
||||||
|
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
||||||
|
$(call cp,core/startos/start-tunneld.service,$(DESTDIR)/lib/systemd/system/start-tunneld.service)
|
||||||
|
|
||||||
|
$(call mkdir,$(DESTDIR)/usr/lib/startos/scripts)
|
||||||
|
$(call cp,build/lib/scripts/forward-port,$(DESTDIR)/usr/lib/startos/scripts/forward-port)
|
||||||
|
|
||||||
|
core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/tunnelbox: $(CORE_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) web/dist/static/start-tunnel/index.html
|
||||||
|
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-tunnelbox.sh
|
||||||
|
|
||||||
deb: results/$(BASENAME).deb
|
deb: results/$(BASENAME).deb
|
||||||
|
|
||||||
debian/control: build/lib/depends build/lib/conflicts
|
results/$(BASENAME).deb: dpkg-build.sh $(call ls-files,debian/startos) $(STARTOS_TARGETS)
|
||||||
./debuild/control.sh
|
|
||||||
|
|
||||||
results/$(BASENAME).deb: dpkg-build.sh $(DEBIAN_SRC) $(ALL_TARGETS)
|
|
||||||
PLATFORM=$(PLATFORM) REQUIRES=debian ./build/os-compat/run-compat.sh ./dpkg-build.sh
|
PLATFORM=$(PLATFORM) REQUIRES=debian ./build/os-compat/run-compat.sh ./dpkg-build.sh
|
||||||
|
|
||||||
|
registry-deb: results/$(REGISTRY_BASENAME).deb
|
||||||
|
|
||||||
|
results/$(REGISTRY_BASENAME).deb: dpkg-build.sh $(call ls-files,debian/start-registry) $(REGISTRY_TARGETS)
|
||||||
|
PROJECT=start-registry PLATFORM=$(ARCH) REQUIRES=debian ./build/os-compat/run-compat.sh ./dpkg-build.sh
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
$(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE)
|
$(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE)
|
||||||
|
|
||||||
squashfs: results/$(BASENAME).squashfs
|
squashfs: results/$(BASENAME).squashfs
|
||||||
|
|
||||||
results/$(BASENAME).$(IMAGE_TYPE) results/$(BASENAME).squashfs: $(IMAGE_RECIPE_SRC) results/$(BASENAME).deb
|
results/$(BASENAME).$(IMAGE_TYPE) results/$(BASENAME).squashfs: $(IMAGE_RECIPE_SRC) results/$(BASENAME).deb
|
||||||
REQUIRES=debian ./build/os-compat/run-compat.sh ./image-recipe/run-local-build.sh "results/$(BASENAME).deb"
|
./image-recipe/run-local-build.sh "results/$(BASENAME).deb"
|
||||||
|
|
||||||
# For creating os images. DO NOT USE
|
# For creating os images. DO NOT USE
|
||||||
install: $(ALL_TARGETS)
|
install: $(STARTOS_TARGETS)
|
||||||
$(call mkdir,$(DESTDIR)/usr/bin)
|
$(call mkdir,$(DESTDIR)/usr/bin)
|
||||||
$(call mkdir,$(DESTDIR)/usr/sbin)
|
$(call mkdir,$(DESTDIR)/usr/sbin)
|
||||||
$(call cp,core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox,$(DESTDIR)/usr/bin/startbox)
|
$(call cp,core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox,$(DESTDIR)/usr/bin/startbox)
|
||||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/startd)
|
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/startd)
|
||||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-cli)
|
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-cli)
|
||||||
if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-musl/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
|
if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-musl/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
|
||||||
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then \
|
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then \
|
||||||
$(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); \
|
$(call cp,cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph,$(DESTDIR)/usr/bin/flamegraph); \
|
||||||
$(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/flamegraph,$(DESTDIR)/usr/bin/flamegraph); \
|
|
||||||
fi
|
fi
|
||||||
$(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs,$(DESTDIR)/usr/bin/startos-backup-fs)
|
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)console($$|-) ]]'; then \
|
||||||
|
$(call cp,cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); \
|
||||||
|
fi
|
||||||
|
$(call cp,cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs,$(DESTDIR)/usr/bin/startos-backup-fs)
|
||||||
$(call ln,/usr/bin/startos-backup-fs,$(DESTDIR)/usr/sbin/mount.backup-fs)
|
$(call ln,/usr/bin/startos-backup-fs,$(DESTDIR)/usr/sbin/mount.backup-fs)
|
||||||
|
|
||||||
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
$(call mkdir,$(DESTDIR)/lib/systemd/system)
|
||||||
@@ -161,7 +201,7 @@ install: $(ALL_TARGETS)
|
|||||||
|
|
||||||
$(call cp,firmware/$(PLATFORM),$(DESTDIR)/usr/lib/startos/firmware)
|
$(call cp,firmware/$(PLATFORM),$(DESTDIR)/usr/lib/startos/firmware)
|
||||||
|
|
||||||
update-overlay: $(ALL_TARGETS)
|
update-overlay: $(STARTOS_TARGETS)
|
||||||
@echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m"
|
@echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m"
|
||||||
@echo "\033[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m"
|
@echo "\033[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m"
|
||||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||||
@@ -170,10 +210,10 @@ update-overlay: $(ALL_TARGETS)
|
|||||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) PLATFORM=$(PLATFORM)
|
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) PLATFORM=$(PLATFORM)
|
||||||
$(call ssh,"sudo systemctl start startd")
|
$(call ssh,"sudo systemctl start startd")
|
||||||
|
|
||||||
wormhole: core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox
|
wormhole: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox
|
||||||
@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 core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo /usr/lib/startos/scripts/chroot-and-upgrade \"cd /usr/bin && rm startbox && wormhole receive --accept-file %s && chmod +x startbox\"\n", $$3 }'
|
@wormhole send core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo /usr/lib/startos/scripts/chroot-and-upgrade \"cd /usr/bin && rm startbox && wormhole receive --accept-file %s && chmod +x startbox\"\n", $$3 }'
|
||||||
|
|
||||||
wormhole-deb: results/$(BASENAME).deb
|
wormhole-deb: results/$(BASENAME).deb
|
||||||
@echo "Paste the following command into the shell of your StartOS server:"
|
@echo "Paste the following command into the shell of your StartOS server:"
|
||||||
@@ -185,18 +225,18 @@ 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: $(ALL_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
|
||||||
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
||||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM)
|
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM)
|
||||||
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync "apt-get install -y $(shell cat ./build/lib/depends)"')
|
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync "apt-get install -y $(shell cat ./build/lib/depends)"')
|
||||||
|
|
||||||
update-startbox: core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox # only update binary (faster than full update)
|
update-startbox: core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox # only update binary (faster than full update)
|
||||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||||
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
||||||
$(call cp,core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox,/media/startos/next/usr/bin/startbox)
|
$(call cp,core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox,/media/startos/next/usr/bin/startbox)
|
||||||
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync true')
|
$(call ssh,'sudo /media/startos/next/usr/lib/startos/scripts/chroot-and-upgrade --no-sync true')
|
||||||
|
|
||||||
update-deb: results/$(BASENAME).deb # better than update, but only available from debian
|
update-deb: results/$(BASENAME).deb # better than update, but only available from debian
|
||||||
@@ -213,9 +253,9 @@ 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: $(ALL_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
|
||||||
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
$(call ssh,'sudo /usr/lib/startos/scripts/chroot-and-upgrade --create')
|
||||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM)
|
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/startos/next PLATFORM=$(PLATFORM)
|
||||||
@@ -236,10 +276,9 @@ container-runtime/node_modules/.package-lock.json: container-runtime/package-loc
|
|||||||
npm --prefix container-runtime ci
|
npm --prefix container-runtime ci
|
||||||
touch container-runtime/node_modules/.package-lock.json
|
touch container-runtime/node_modules/.package-lock.json
|
||||||
|
|
||||||
sdk/base/lib/osBindings/index.ts: $(shell if [ "$(REBUILD_TYPES)" -ne 0 ]; then echo core/startos/bindings/index.ts; fi)
|
ts-bindings: core/startos/bindings/index.ts
|
||||||
mkdir -p sdk/base/lib/osBindings
|
mkdir -p sdk/base/lib/osBindings
|
||||||
rsync -ac --delete core/startos/bindings/ sdk/base/lib/osBindings/
|
rsync -ac --delete core/startos/bindings/ sdk/base/lib/osBindings/
|
||||||
touch sdk/base/lib/osBindings/index.ts
|
|
||||||
|
|
||||||
core/startos/bindings/index.ts: $(call ls-files, core) $(ENVIRONMENT_FILE)
|
core/startos/bindings/index.ts: $(call ls-files, core) $(ENVIRONMENT_FILE)
|
||||||
rm -rf core/startos/bindings
|
rm -rf core/startos/bindings
|
||||||
@@ -261,22 +300,22 @@ container-runtime/dist/node_modules/.package-lock.json container-runtime/dist/pa
|
|||||||
./container-runtime/install-dist-deps.sh
|
./container-runtime/install-dist-deps.sh
|
||||||
touch container-runtime/dist/node_modules/.package-lock.json
|
touch container-runtime/dist/node_modules/.package-lock.json
|
||||||
|
|
||||||
container-runtime/rootfs.$(ARCH).squashfs: container-runtime/debian.$(ARCH).squashfs container-runtime/container-runtime.service container-runtime/update-image.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules/.package-lock.json core/target/$(ARCH)-unknown-linux-musl/release/containerbox
|
container-runtime/rootfs.$(ARCH).squashfs: container-runtime/debian.$(ARCH).squashfs container-runtime/container-runtime.service container-runtime/update-image.sh container-runtime/deb-install.sh container-runtime/dist/index.js container-runtime/dist/node_modules/.package-lock.json core/target/$(RUST_ARCH)-unknown-linux-musl/release/containerbox
|
||||||
ARCH=$(ARCH) REQUIRES=linux ./build/os-compat/run-compat.sh ./container-runtime/update-image.sh
|
ARCH=$(ARCH) REQUIRES=linux ./build/os-compat/run-compat.sh ./container-runtime/update-image.sh
|
||||||
|
|
||||||
build/lib/depends build/lib/conflicts: $(ENVIRONMENT_FILE) build/dpkg-deps/*
|
build/lib/depends build/lib/conflicts: $(ENVIRONMENT_FILE) $(PLATFORM_FILE) $(shell ls build/dpkg-deps/*)
|
||||||
build/dpkg-deps/generate.sh
|
PLATFORM=$(PLATFORM) ARCH=$(ARCH) build/dpkg-deps/generate.sh
|
||||||
|
|
||||||
$(FIRMWARE_ROMS): build/lib/firmware.json download-firmware.sh $(PLATFORM_FILE)
|
$(FIRMWARE_ROMS): build/lib/firmware.json download-firmware.sh $(PLATFORM_FILE)
|
||||||
./download-firmware.sh $(PLATFORM)
|
./download-firmware.sh $(PLATFORM)
|
||||||
|
|
||||||
core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE)
|
core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE)
|
||||||
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-startbox.sh
|
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-startbox.sh
|
||||||
touch core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox
|
touch core/target/$(RUST_ARCH)-unknown-linux-musl/$(PROFILE)/startbox
|
||||||
|
|
||||||
core/target/$(ARCH)-unknown-linux-musl/release/containerbox: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
core/target/$(RUST_ARCH)-unknown-linux-musl/release/containerbox: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
ARCH=$(ARCH) ./core/build-containerbox.sh
|
ARCH=$(ARCH) ./core/build-containerbox.sh
|
||||||
touch core/target/$(ARCH)-unknown-linux-musl/release/containerbox
|
touch core/target/$(RUST_ARCH)-unknown-linux-musl/release/containerbox
|
||||||
|
|
||||||
web/package-lock.json: web/package.json sdk/baseDist/package.json
|
web/package-lock.json: web/package.json sdk/baseDist/package.json
|
||||||
npm --prefix web i
|
npm --prefix web i
|
||||||
@@ -303,8 +342,12 @@ web/dist/raw/install-wizard/index.html: $(WEB_INSTALL_WIZARD_SRC) $(WEB_SHARED_S
|
|||||||
npm --prefix web run build:install
|
npm --prefix web run build:install
|
||||||
touch web/dist/raw/install-wizard/index.html
|
touch web/dist/raw/install-wizard/index.html
|
||||||
|
|
||||||
$(COMPRESSED_WEB_UIS): $(WEB_UIS) $(ENVIRONMENT_FILE)
|
web/dist/raw/start-tunnel/index.html: $(WEB_START_TUNNEL_SRC) $(WEB_SHARED_SRC) web/.angular/.updated
|
||||||
./compress-uis.sh
|
npm --prefix web run build:tunnel
|
||||||
|
touch web/dist/raw/start-tunnel/index.html
|
||||||
|
|
||||||
|
web/dist/static/%/index.html: web/dist/raw/%/index.html
|
||||||
|
./compress-uis.sh $*
|
||||||
|
|
||||||
web/config.json: $(GIT_HASH_FILE) web/config-sample.json
|
web/config.json: $(GIT_HASH_FILE) web/config-sample.json
|
||||||
jq '.useMocks = false' web/config-sample.json | jq '.gitHash = "$(shell cat GIT_HASH.txt)"' > web/config.json
|
jq '.useMocks = false' web/config-sample.json | jq '.gitHash = "$(shell cat GIT_HASH.txt)"' > web/config.json
|
||||||
@@ -331,11 +374,11 @@ ui: web/dist/raw/ui
|
|||||||
cargo-deps/aarch64-unknown-linux-musl/release/pi-beep:
|
cargo-deps/aarch64-unknown-linux-musl/release/pi-beep:
|
||||||
ARCH=aarch64 ./build-cargo-dep.sh pi-beep
|
ARCH=aarch64 ./build-cargo-dep.sh pi-beep
|
||||||
|
|
||||||
cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console:
|
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/tokio-console:
|
||||||
ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh tokio-console
|
ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh tokio-console
|
||||||
|
|
||||||
cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs:
|
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/startos-backup-fs:
|
||||||
ARCH=$(ARCH) PREINSTALL="apk add fuse3 fuse3-dev fuse3-static musl-dev pkgconfig" ./build-cargo-dep.sh --git https://github.com/Start9Labs/start-fs.git startos-backup-fs
|
ARCH=$(ARCH) PREINSTALL="apk add fuse3 fuse3-dev fuse3-static musl-dev pkgconfig" ./build-cargo-dep.sh --git https://github.com/Start9Labs/start-fs.git startos-backup-fs
|
||||||
|
|
||||||
cargo-deps/$(ARCH)-unknown-linux-musl/release/flamegraph:
|
cargo-deps/$(RUST_ARCH)-unknown-linux-musl/release/flamegraph:
|
||||||
ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh flamegraph
|
ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh flamegraph
|
||||||
77
START-TUNNEL.md
Normal file
77
START-TUNNEL.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# StartTunnel
|
||||||
|
|
||||||
|
A self-hosted WireGuard VPN optimized for creating VLANs and reverse tunneling to personal servers.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
- It must have a dedicated public IP address.
|
||||||
|
- For compute (CPU), memory (RAM), and storage (disk), choose the minimum spec.
|
||||||
|
- For transfer (bandwidth), it depends on (1) your use case and (2) your home Internet's _upload_ speed. Even if you intend to serve large files or stream content from your server, there is no reason to pay for speeds that exceed your home Internet's upload speed.
|
||||||
|
|
||||||
|
1. Provision the VPS with the latest version of Debian.
|
||||||
|
|
||||||
|
1. Access the VPS via SSH.
|
||||||
|
|
||||||
|
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-unknown.dev_$(uname -m).deb && apt-get install -y ./start-tunnel-0.4.0-alpha.12-unknown.dev_$(uname -m).deb) && rm -rf $TMP_DIR && systemctl start start-tunneld && echo "Installation Succeeded"
|
||||||
|
```
|
||||||
|
|
||||||
|
5. [Initialize the web interface](#web-interface) (recommended)
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
```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-unknown.dev_$(uname -m).deb && apt-get install --reinstall -y ./start-tunnel-0.4.0-alpha.12-unknown.dev_$(uname -m).deb) && rm -rf $TMP_DIR && systemctl daemon-reload && systemctl restart start-tunneld && echo "Update Succeeded"
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
|
||||||
|
By default, StartTunnel is managed via the `start-tunnel` command line interface, which is self-documented.
|
||||||
|
|
||||||
|
```
|
||||||
|
start-tunnel --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Web Interface
|
||||||
|
|
||||||
|
If you choose to enable the web interface (recommended in most cases), StartTunnel can be accessed as a website from the browser, or programmatically via API.
|
||||||
|
|
||||||
|
1. Initialize the web interface.
|
||||||
|
|
||||||
|
start-tunnel web init
|
||||||
|
|
||||||
|
1. When prompted, select the IP address at which to host the web interface. In many cases, there will be only one IP address.
|
||||||
|
|
||||||
|
1. When prompted, enter the port at which to host the web interface. The default is 8443, and we recommend using it. If you change the default, choose an uncommon port to avoid conflicts.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
- <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,5 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
PROJECT=${PROJECT:-"startos"}
|
||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
PLATFORM="$(if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)"
|
PLATFORM="$(if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)"
|
||||||
@@ -16,4 +18,4 @@ if [ -n "$STARTOS_ENV" ]; then
|
|||||||
VERSION_FULL="$VERSION_FULL~${STARTOS_ENV}"
|
VERSION_FULL="$VERSION_FULL~${STARTOS_ENV}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -n "startos-${VERSION_FULL}_${PLATFORM}"
|
echo -n "${PROJECT}-${VERSION_FULL}_${PLATFORM}"
|
||||||
@@ -7,9 +7,9 @@ bmon
|
|||||||
btrfs-progs
|
btrfs-progs
|
||||||
ca-certificates
|
ca-certificates
|
||||||
cifs-utils
|
cifs-utils
|
||||||
|
conntrack
|
||||||
cryptsetup
|
cryptsetup
|
||||||
curl
|
curl
|
||||||
dnsutils
|
|
||||||
dmidecode
|
dmidecode
|
||||||
dnsutils
|
dnsutils
|
||||||
dosfstools
|
dosfstools
|
||||||
@@ -19,6 +19,7 @@ exfatprogs
|
|||||||
flashrom
|
flashrom
|
||||||
fuse3
|
fuse3
|
||||||
grub-common
|
grub-common
|
||||||
|
grub-efi
|
||||||
htop
|
htop
|
||||||
httpdirfs
|
httpdirfs
|
||||||
iotop
|
iotop
|
||||||
@@ -41,7 +42,6 @@ nvme-cli
|
|||||||
nyx
|
nyx
|
||||||
openssh-server
|
openssh-server
|
||||||
podman
|
podman
|
||||||
postgresql
|
|
||||||
psmisc
|
psmisc
|
||||||
qemu-guest-agent
|
qemu-guest-agent
|
||||||
rfkill
|
rfkill
|
||||||
|
|||||||
@@ -5,11 +5,15 @@ set -e
|
|||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
IFS="-" read -ra FEATURES <<< "$ENVIRONMENT"
|
IFS="-" read -ra FEATURES <<< "$ENVIRONMENT"
|
||||||
|
FEATURES+=("${ARCH}")
|
||||||
|
if [ "$ARCH" != "$PLATFORM" ]; then
|
||||||
|
FEATURES+=("${PLATFORM}")
|
||||||
|
fi
|
||||||
|
|
||||||
feature_file_checker='
|
feature_file_checker='
|
||||||
/^#/ { next }
|
/^#/ { next }
|
||||||
/^\+ [a-z0-9-]+$/ { next }
|
/^\+ [a-z0-9.-]+$/ { next }
|
||||||
/^- [a-z0-9-]+$/ { next }
|
/^- [a-z0-9.-]+$/ { next }
|
||||||
{ exit 1 }
|
{ exit 1 }
|
||||||
'
|
'
|
||||||
|
|
||||||
@@ -30,8 +34,8 @@ for type in conflicts depends; do
|
|||||||
for feature in ${FEATURES[@]}; do
|
for feature in ${FEATURES[@]}; do
|
||||||
file="$feature.$type"
|
file="$feature.$type"
|
||||||
if [ -f $file ]; then
|
if [ -f $file ]; then
|
||||||
if grep "^- $pkg$" $file; then
|
if grep "^- $pkg$" $file > /dev/null; then
|
||||||
SKIP=1
|
SKIP=yes
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
10
build/dpkg-deps/raspberrypi.depends
Normal file
10
build/dpkg-deps/raspberrypi.depends
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
- grub-efi
|
||||||
|
+ parted
|
||||||
|
+ raspberrypi-net-mods
|
||||||
|
+ raspberrypi-sys-mods
|
||||||
|
+ raspi-config
|
||||||
|
+ raspi-firmware
|
||||||
|
+ raspi-utils
|
||||||
|
+ rpi-eeprom
|
||||||
|
+ rpi-update
|
||||||
|
+ rpi.gpio-common
|
||||||
1
build/dpkg-deps/x86_64.depends
Normal file
1
build/dpkg-deps/x86_64.depends
Normal file
@@ -0,0 +1 @@
|
|||||||
|
+ grub-pc-bin
|
||||||
@@ -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
|
||||||
@@ -64,9 +64,11 @@ user_pref("messaging-system.rsexperimentloader.enabled", false);
|
|||||||
user_pref("network.allow-experiments", false);
|
user_pref("network.allow-experiments", false);
|
||||||
user_pref("network.captive-portal-service.enabled", false);
|
user_pref("network.captive-portal-service.enabled", false);
|
||||||
user_pref("network.connectivity-service.enabled", false);
|
user_pref("network.connectivity-service.enabled", false);
|
||||||
user_pref("network.proxy.autoconfig_url", "file:///usr/lib/startos/proxy.pac");
|
user_pref("network.proxy.socks", "10.0.3.1");
|
||||||
|
user_pref("network.proxy.socks_port", 9050);
|
||||||
|
user_pref("network.proxy.socks_version", 5);
|
||||||
user_pref("network.proxy.socks_remote_dns", true);
|
user_pref("network.proxy.socks_remote_dns", true);
|
||||||
user_pref("network.proxy.type", 2);
|
user_pref("network.proxy.type", 1);
|
||||||
user_pref("privacy.resistFingerprinting", true);
|
user_pref("privacy.resistFingerprinting", true);
|
||||||
//Enable letterboxing if we want the window size sent to the server to snap to common resolutions:
|
//Enable letterboxing if we want the window size sent to the server to snap to common resolutions:
|
||||||
//user_pref("privacy.resistFingerprinting.letterboxing", true);
|
//user_pref("privacy.resistFingerprinting.letterboxing", true);
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
if [ -z "$iiface" ] || [ -z "$oiface" ] || [ -z "$sip" ] || [ -z "$dip" ] || [ -z "$sport" ] || [ -z "$dport" ]; then
|
if [ -z "$sip" ] || [ -z "$dip" ] || [ -z "$sport" ] || [ -z "$dport" ]; then
|
||||||
>&2 echo 'missing required env var'
|
>&2 echo 'missing required env var'
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
kind="-A"
|
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
|
||||||
|
if ! rule_exists "$@"; then
|
||||||
|
iptables -t nat -A "$@"
|
||||||
|
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
|
if [ "$UNDO" = 1 ]; then
|
||||||
kind="-D"
|
conntrack -D -p tcp -d $sip --dport $sport || true # conntrack returns exit 1 if no connections are active
|
||||||
fi
|
fi
|
||||||
|
|
||||||
iptables -t nat "$kind" POSTROUTING -o $iiface -j MASQUERADE
|
|
||||||
iptables -t nat "$kind" PREROUTING -i $iiface -p tcp --dport $sport -j DNAT --to-destination $dip:$dport
|
|
||||||
iptables -t nat "$kind" PREROUTING -i $iiface -p udp --dport $sport -j DNAT --to-destination $dip:$dport
|
|
||||||
iptables -t nat "$kind" PREROUTING -i $oiface -s $dip/24 -d $sip -p tcp --dport $sport -j DNAT --to-destination $dip:$dport
|
|
||||||
iptables -t nat "$kind" PREROUTING -i $oiface -s $dip/24 -d $sip -p udp --dport $sport -j DNAT --to-destination $dip:$dport
|
|
||||||
iptables -t nat "$kind" POSTROUTING -o $oiface -s $dip/24 -d $dip/32 -p tcp --dport $dport -j SNAT --to-source $sip:$sport
|
|
||||||
iptables -t nat "$kind" POSTROUTING -o $oiface -s $dip/24 -d $dip/32 -p udp --dport $dport -j SNAT --to-source $sip:$sport
|
|
||||||
|
|
||||||
|
|
||||||
iptables -t nat "$kind" PREROUTING -i $iiface -s $sip/32 -d $sip -p tcp --dport $sport -j DNAT --to-destination $dip:$dport
|
|
||||||
iptables -t nat "$kind" PREROUTING -i $iiface -s $sip/32 -d $sip -p udp --dport $sport -j DNAT --to-destination $dip:$dport
|
|
||||||
iptables -t nat "$kind" POSTROUTING -o $oiface -s $sip/32 -d $dip/32 -p tcp --dport $dport -j SNAT --to-source $sip:$sport
|
|
||||||
iptables -t nat "$kind" POSTROUTING -o $oiface -s $sip/32 -d $dip/32 -p udp --dport $dport -j SNAT --to-source $sip:$sport
|
|
||||||
@@ -83,6 +83,7 @@ local_mount_root()
|
|||||||
if [ -d "$image" ]; then
|
if [ -d "$image" ]; then
|
||||||
mount -r --bind $image /lower
|
mount -r --bind $image /lower
|
||||||
elif [ -f "$image" ]; then
|
elif [ -f "$image" ]; then
|
||||||
|
modprobe loop
|
||||||
modprobe squashfs
|
modprobe squashfs
|
||||||
mount -r $image /lower
|
mount -r $image /lower
|
||||||
else
|
else
|
||||||
|
|||||||
82
build/lib/scripts/upgrade
Executable file
82
build/lib/scripts/upgrade
Executable 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...'
|
||||||
@@ -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
|
|
||||||
@@ -18,7 +18,7 @@ if [ "$FORCE_COMPAT" = 1 ] || ( [ "$REQUIRES" = "linux" ] && [ "$(uname -s)" !=
|
|||||||
|
|
||||||
docker run -d --rm --name os-compat --privileged --security-opt apparmor=unconfined -v "${project_pwd}:/root/start-os" -v /lib/modules:/lib/modules:ro start9/build-env
|
docker run -d --rm --name os-compat --privileged --security-opt apparmor=unconfined -v "${project_pwd}:/root/start-os" -v /lib/modules:/lib/modules:ro start9/build-env
|
||||||
while ! docker exec os-compat systemctl is-active --quiet multi-user.target 2> /dev/null; do sleep .5; done
|
while ! docker exec os-compat systemctl is-active --quiet multi-user.target 2> /dev/null; do sleep .5; done
|
||||||
docker exec -eARCH -eENVIRONMENT -ePLATFORM -eGIT_BRANCH_AS_HASH $USE_TTY -w "/root/start-os${rel_pwd}" os-compat $@
|
docker exec -eARCH -eENVIRONMENT -ePLATFORM -eGIT_BRANCH_AS_HASH -ePROJECT -eDEPENDS -eCONFLICTS $USE_TTY -w "/root/start-os${rel_pwd}" os-compat $@
|
||||||
code=$?
|
code=$?
|
||||||
docker stop os-compat
|
docker stop os-compat
|
||||||
exit $code
|
exit $code
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
rm -rf web/dist/static
|
STATIC_DIR=web/dist/static/$1
|
||||||
|
RAW_DIR=web/dist/raw/$1
|
||||||
|
|
||||||
|
mkdir -p $STATIC_DIR
|
||||||
|
rm -rf $STATIC_DIR
|
||||||
|
|
||||||
if ! [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
|
if ! [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
|
||||||
find web/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 gzip -kf
|
find $RAW_DIR -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 gzip -kf
|
||||||
find web/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 brotli -kf
|
find $RAW_DIR -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 brotli -kf
|
||||||
|
|
||||||
for file in $(find web/dist/raw -type f -not -name '*.gz' -and -not -name '*.br'); do
|
for file in $(find $RAW_DIR -type f -not -name '*.gz' -and -not -name '*.br'); do
|
||||||
raw_size=$(du $file | awk '{print $1 * 512}')
|
raw_size=$(du $file | awk '{print $1 * 512}')
|
||||||
gz_size=$(du $file.gz | awk '{print $1 * 512}')
|
gz_size=$(du $file.gz | awk '{print $1 * 512}')
|
||||||
br_size=$(du $file.br | awk '{print $1 * 512}')
|
br_size=$(du $file.br | awk '{print $1 * 512}')
|
||||||
@@ -23,4 +27,5 @@ if ! [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cp -r web/dist/raw web/dist/static
|
|
||||||
|
cp -r $RAW_DIR $STATIC_DIR
|
||||||
@@ -2,17 +2,11 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
|
||||||
mkdir -p /run/systemd/resolve
|
mkdir -p /run/systemd/resolve
|
||||||
echo "nameserver 8.8.8.8" > /run/systemd/resolve/stub-resolv.conf
|
echo "nameserver 8.8.8.8" > /run/systemd/resolve/stub-resolv.conf
|
||||||
|
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y curl rsync qemu-user-static
|
apt-get install -y curl rsync qemu-user-static nodejs
|
||||||
|
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
|
|
||||||
source ~/.bashrc
|
|
||||||
nvm install 22
|
|
||||||
ln -s $(which node) /usr/bin/node
|
|
||||||
|
|
||||||
sed -i '/\(^\|#\)DNSStubListener=/c\DNSStubListener=no' /etc/systemd/resolved.conf
|
sed -i '/\(^\|#\)DNSStubListener=/c\DNSStubListener=no' /etc/systemd/resolved.conf
|
||||||
sed -i '/\(^\|#\)Storage=/c\Storage=persistent' /etc/systemd/journald.conf
|
sed -i '/\(^\|#\)Storage=/c\Storage=persistent' /etc/systemd/journald.conf
|
||||||
@@ -24,5 +18,5 @@ systemctl enable container-runtime.service
|
|||||||
|
|
||||||
rm -rf /run/systemd
|
rm -rf /run/systemd
|
||||||
|
|
||||||
rm /etc/resolv.conf
|
rm -f /etc/resolv.conf
|
||||||
echo "nameserver 10.0.3.1" > /etc/resolv.conf
|
echo "nameserver 10.0.3.1" > /etc/resolv.conf
|
||||||
@@ -3,7 +3,7 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
DISTRO=debian
|
DISTRO=debian
|
||||||
VERSION=bookworm
|
VERSION=trixie
|
||||||
ARCH=${ARCH:-$(uname -m)}
|
ARCH=${ARCH:-$(uname -m)}
|
||||||
FLAVOR=default
|
FLAVOR=default
|
||||||
|
|
||||||
|
|||||||
7
container-runtime/package-lock.json
generated
7
container-runtime/package-lock.json
generated
@@ -38,7 +38,7 @@
|
|||||||
},
|
},
|
||||||
"../sdk/dist": {
|
"../sdk/dist": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.38",
|
"version": "0.4.0-beta.44",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
@@ -110,6 +110,7 @@
|
|||||||
"integrity": "sha512-l+lkXCHS6tQEc5oUpK28xBOZ6+HwaH7YwoYQbLFiYb4nS2/l1tKnZEtEWkD0GuiYdvArf9qBS0XlQGXzPMsNqQ==",
|
"integrity": "sha512-l+lkXCHS6tQEc5oUpK28xBOZ6+HwaH7YwoYQbLFiYb4nS2/l1tKnZEtEWkD0GuiYdvArf9qBS0XlQGXzPMsNqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.26.2",
|
"@babel/code-frame": "^7.26.2",
|
||||||
@@ -1200,6 +1201,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@swc/counter": "^0.1.3",
|
"@swc/counter": "^0.1.3",
|
||||||
"@swc/types": "^0.1.17"
|
"@swc/types": "^0.1.17"
|
||||||
@@ -2143,6 +2145,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001688",
|
"caniuse-lite": "^1.0.30001688",
|
||||||
"electron-to-chromium": "^1.5.73",
|
"electron-to-chromium": "^1.5.73",
|
||||||
@@ -3990,6 +3993,7 @@
|
|||||||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/core": "^29.7.0",
|
"@jest/core": "^29.7.0",
|
||||||
"@jest/types": "^29.6.3",
|
"@jest/types": "^29.6.3",
|
||||||
@@ -6556,6 +6560,7 @@
|
|||||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|||||||
@@ -158,6 +158,8 @@ export class RpcListener {
|
|||||||
|
|
||||||
this.unixSocketServer.listen(SOCKET_PATH)
|
this.unixSocketServer.listen(SOCKET_PATH)
|
||||||
|
|
||||||
|
console.log("Listening on %s", SOCKET_PATH)
|
||||||
|
|
||||||
this.unixSocketServer.on("connection", (s) => {
|
this.unixSocketServer.on("connection", (s) => {
|
||||||
let id: IdType = null
|
let id: IdType = null
|
||||||
const captureId = <X>(x: X) => {
|
const captureId = <X>(x: X) => {
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if mountpoint -q tmp/combined; then sudo umount -R tmp/combined; fi
|
RUST_ARCH="$ARCH"
|
||||||
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
|
RUST_ARCH="riscv64gc"
|
||||||
|
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
|
||||||
@@ -39,7 +44,7 @@ sudo cp container-runtime.service tmp/combined/lib/systemd/system/container-runt
|
|||||||
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime.service
|
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime.service
|
||||||
sudo cp container-runtime-failure.service tmp/combined/lib/systemd/system/container-runtime-failure.service
|
sudo cp container-runtime-failure.service tmp/combined/lib/systemd/system/container-runtime-failure.service
|
||||||
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime-failure.service
|
sudo chown 0:0 tmp/combined/lib/systemd/system/container-runtime-failure.service
|
||||||
sudo cp ../core/target/$ARCH-unknown-linux-musl/release/containerbox tmp/combined/usr/bin/start-container
|
sudo cp ../core/target/${RUST_ARCH}-unknown-linux-musl/release/containerbox tmp/combined/usr/bin/start-container
|
||||||
echo -e '#!/bin/bash\nexec start-container "$@"' | sudo tee tmp/combined/usr/bin/start-cli # TODO: remove
|
echo -e '#!/bin/bash\nexec start-container "$@"' | sudo tee tmp/combined/usr/bin/start-cli # TODO: remove
|
||||||
sudo chmod +x tmp/combined/usr/bin/start-cli
|
sudo chmod +x tmp/combined/usr/bin/start-cli
|
||||||
sudo chown 0:0 tmp/combined/usr/bin/start-container
|
sudo chown 0:0 tmp/combined/usr/bin/start-container
|
||||||
|
|||||||
3482
core/Cargo.lock
generated
3482
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
2
core/Cross.toml
Normal file
2
core/Cross.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[build]
|
||||||
|
pre-build = ["apt-get update && apt-get install -y rsync"]
|
||||||
@@ -2,36 +2,49 @@
|
|||||||
|
|
||||||
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}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "${ARCH:-}" ]; then
|
if [ -z "${ARCH:-}" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$ARCH" = "arm64" ]; then
|
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
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
USE_TTY=
|
|
||||||
if tty -s; then
|
|
||||||
USE_TTY="-it"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
# Ensure GIT_HASH.txt exists if not created by higher-level build steps
|
# Ensure GIT_HASH.txt exists if not created by higher-level build steps
|
||||||
@@ -46,19 +59,13 @@ if [ -n "$FEATURES" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
if [[ "${ENVIRONMENT:-}" =~ (^|-)unstable($|-) ]]; then
|
if [[ "${ENVIRONMENT:-}" =~ (^|-)console($|-) ]]; then
|
||||||
RUSTFLAGS="--cfg tokio_unstable"
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command -v zig >/dev/null 2>&1 && [ "${ENFORCE_USE_DOCKER:-0}" != "1" ]; then
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
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
|
||||||
RUSTFLAGS=$RUSTFLAGS sh -c "cd core && cargo zigbuild --release --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
|
||||||
else
|
rust-zig-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
alias 'rust-zig-builder'='docker run '"$USE_TTY"' --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/cargo-zigbuild'
|
|
||||||
RUSTFLAGS=$RUSTFLAGS rust-zig-builder sh -c "cd core && cargo zigbuild --release --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
|
|
||||||
fi
|
fi
|
||||||
@@ -2,9 +2,21 @@
|
|||||||
|
|
||||||
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}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -13,24 +25,22 @@ if [ "$ARCH" = "arm64" ]; then
|
|||||||
ARCH="aarch64"
|
ARCH="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
USE_TTY=
|
RUST_ARCH="$ARCH"
|
||||||
if tty -s; then
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
USE_TTY="-it"
|
RUST_ARCH="riscv64gc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
|
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
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\""
|
||||||
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli-container,$FEATURES --locked --bin containerbox --target=$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/$ARCH-unknown-linux-musl/release/containerbox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/containerbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
@@ -2,9 +2,21 @@
|
|||||||
|
|
||||||
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}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -13,24 +25,22 @@ if [ "$ARCH" = "arm64" ]; then
|
|||||||
ARCH="aarch64"
|
ARCH="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
USE_TTY=
|
RUST_ARCH="$ARCH"
|
||||||
if tty -s; then
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
USE_TTY="-it"
|
RUST_ARCH="riscv64gc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
|
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
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\""
|
||||||
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli-registry,registry,$FEATURES --locked --bin registrybox --target=$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/$ARCH-unknown-linux-musl/release/registrybox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/registrybox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
@@ -1,15 +1,22 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
source ./builder-alias.sh
|
||||||
|
|
||||||
|
set -ea
|
||||||
|
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 "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
|
||||||
|
|
||||||
set -ea
|
|
||||||
shopt -s expand_aliases
|
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -18,24 +25,22 @@ if [ "$ARCH" = "arm64" ]; then
|
|||||||
ARCH="aarch64"
|
ARCH="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
USE_TTY=
|
RUST_ARCH="$ARCH"
|
||||||
if tty -s; then
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
USE_TTY="-it"
|
RUST_ARCH="riscv64gc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
|
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
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\""
|
||||||
rust-musl-builder sh -c "cd core && cargo build $BUILD_FLAGS --no-default-features --features cli,startd,$FEATURES --locked --bin startbox --target=$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/$ARCH-unknown-linux-musl/${PROFILE}/startbox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/startbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
@@ -2,35 +2,43 @@
|
|||||||
|
|
||||||
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}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$ARCH" = "arm64" ]; then
|
if [ "$ARCH" = "arm64" ]; then
|
||||||
ARCH="aarch64"
|
ARCH="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
USE_TTY=
|
RUST_ARCH="$ARCH"
|
||||||
if tty -s; then
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
USE_TTY="-it"
|
RUST_ARCH="riscv64gc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
|
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; 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\""
|
||||||
rust-musl-builder sh -c "cd core && cargo test --release --features=test,$FEATURES 'export_bindings_' && chown \$UID:\$UID startos/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
|
if [ "$(ls -nd "core/startos/bindings" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID startos/bindings && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
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
|
fi
|
||||||
@@ -2,9 +2,21 @@
|
|||||||
|
|
||||||
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}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -13,24 +25,22 @@ if [ "$ARCH" = "arm64" ]; then
|
|||||||
ARCH="aarch64"
|
ARCH="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
USE_TTY=
|
RUST_ARCH="$ARCH"
|
||||||
if tty -s; then
|
if [ "$ARCH" = "riscv64" ]; then
|
||||||
USE_TTY="-it"
|
RUST_ARCH="riscv64gc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
|
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
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\""
|
||||||
rust-musl-builder sh -c "cd core && cargo build --release --no-default-features --features cli-tunnel,tunnel,$FEATURES --locked --bin tunnelbox --target=$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/$ARCH-unknown-linux-musl/release/tunnelbox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd "core/target/$RUST_ARCH-unknown-linux-musl/$PROFILE/tunnelbox" | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
@@ -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 "AWS_LC_SYS_CMAKE_TOOLCHAIN_FILE_riscv64gc_unknown_linux_musl=/root/cmake-overrides/toolchain-riscv64-musl-clang.cmake" -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'
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
[package]
|
[package]
|
||||||
|
edition = "2021"
|
||||||
name = "models"
|
name = "models"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
arti = ["arti-client"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arti-client = { version = "0.33", default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
arti-client = { version = "0.33", default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
axum = "0.8.4"
|
axum = "0.8.4"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
ed25519-dalek = { version = "2.0.0", features = ["serde"] }
|
ed25519-dalek = { version = "2.0.0", features = ["serde"] }
|
||||||
gpt = "4.1.0"
|
|
||||||
lazy_static = "1.4"
|
|
||||||
mbrman = "0.6.0"
|
|
||||||
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
|
gpt = "4.1.0"
|
||||||
ipnet = "2.8.0"
|
ipnet = "2.8.0"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
lettre = { version = "0.11", default-features = false }
|
||||||
|
mbrman = "0.6.0"
|
||||||
|
miette = "7.6.0"
|
||||||
num_enum = "0.7.1"
|
num_enum = "0.7.1"
|
||||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
openssl = { version = "0.10.57", features = ["vendored"] }
|
||||||
patch-db = { version = "*", path = "../../patch-db/patch-db", features = [
|
patch-db = { version = "*", path = "../../patch-db/patch-db", features = [
|
||||||
@@ -31,9 +36,11 @@ rustls = "0.23"
|
|||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
ssh-key = "0.6.2"
|
ssh-key = "0.6.2"
|
||||||
ts-rs = "9"
|
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
torut = "0.2.1"
|
||||||
tracing = "0.1.39"
|
tracing = "0.1.39"
|
||||||
|
ts-rs = "9"
|
||||||
|
typeid = "1"
|
||||||
yasi = { version = "0.1.6", features = ["serde", "ts-rs"] }
|
yasi = { version = "0.1.6", features = ["serde", "ts-rs"] }
|
||||||
zbus = "5"
|
zbus = "5"
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ pub enum ErrorKind {
|
|||||||
DBus = 75,
|
DBus = 75,
|
||||||
InstallFailed = 76,
|
InstallFailed = 76,
|
||||||
UpdateFailed = 77,
|
UpdateFailed = 77,
|
||||||
|
Smtp = 78,
|
||||||
}
|
}
|
||||||
impl ErrorKind {
|
impl ErrorKind {
|
||||||
pub fn as_str(&self) -> &'static str {
|
pub fn as_str(&self) -> &'static str {
|
||||||
@@ -176,6 +177,7 @@ impl ErrorKind {
|
|||||||
DBus => "DBus Error",
|
DBus => "DBus Error",
|
||||||
InstallFailed => "Install Failed",
|
InstallFailed => "Install Failed",
|
||||||
UpdateFailed => "Update Failed",
|
UpdateFailed => "Update Failed",
|
||||||
|
Smtp => "SMTP Error",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,9 +187,9 @@ impl Display for ErrorKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
pub source: color_eyre::eyre::Error,
|
pub source: color_eyre::eyre::Error,
|
||||||
|
pub debug: Option<color_eyre::eyre::Error>,
|
||||||
pub kind: ErrorKind,
|
pub kind: ErrorKind,
|
||||||
pub revision: Option<Revision>,
|
pub revision: Option<Revision>,
|
||||||
pub task: Option<JoinHandle<()>>,
|
pub task: Option<JoinHandle<()>>,
|
||||||
@@ -195,13 +197,29 @@ pub struct Error {
|
|||||||
|
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}: {}", self.kind.as_str(), self.source)
|
write!(f, "{}: {:#}", self.kind.as_str(), self.source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Debug for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}: {:?}",
|
||||||
|
self.kind.as_str(),
|
||||||
|
self.debug.as_ref().unwrap_or(&self.source)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn new<E: Into<color_eyre::eyre::Error>>(source: E, kind: ErrorKind) -> Self {
|
pub fn new<E: Into<color_eyre::eyre::Error> + std::fmt::Debug + 'static>(
|
||||||
|
source: E,
|
||||||
|
kind: ErrorKind,
|
||||||
|
) -> Self {
|
||||||
|
let debug = (typeid::of::<E>() == typeid::of::<color_eyre::eyre::Error>())
|
||||||
|
.then(|| eyre!("{source:?}"));
|
||||||
Error {
|
Error {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
debug,
|
||||||
kind,
|
kind,
|
||||||
revision: None,
|
revision: None,
|
||||||
task: None,
|
task: None,
|
||||||
@@ -209,11 +227,8 @@ impl Error {
|
|||||||
}
|
}
|
||||||
pub fn clone_output(&self) -> Self {
|
pub fn clone_output(&self) -> Self {
|
||||||
Error {
|
Error {
|
||||||
source: ErrorData {
|
source: eyre!("{}", self.source),
|
||||||
details: format!("{}", self.source),
|
debug: self.debug.as_ref().map(|e| eyre!("{e}")),
|
||||||
debug: format!("{:?}", self.source),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
kind: self.kind,
|
kind: self.kind,
|
||||||
revision: self.revision.clone(),
|
revision: self.revision.clone(),
|
||||||
task: None,
|
task: None,
|
||||||
@@ -343,11 +358,17 @@ impl From<reqwest::Error> for Error {
|
|||||||
Error::new(e, kind)
|
Error::new(e, kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "arti")]
|
||||||
impl From<arti_client::Error> for Error {
|
impl From<arti_client::Error> for Error {
|
||||||
fn from(e: arti_client::Error) -> Self {
|
fn from(e: arti_client::Error) -> Self {
|
||||||
Error::new(e, ErrorKind::Tor)
|
Error::new(e, ErrorKind::Tor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<torut::control::ConnError> for Error {
|
||||||
|
fn from(e: torut::control::ConnError) -> Self {
|
||||||
|
Error::new(e, ErrorKind::Tor)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl From<zbus::Error> for Error {
|
impl From<zbus::Error> for Error {
|
||||||
fn from(e: zbus::Error) -> Self {
|
fn from(e: zbus::Error) -> Self {
|
||||||
Error::new(e, ErrorKind::DBus)
|
Error::new(e, ErrorKind::DBus)
|
||||||
@@ -358,6 +379,21 @@ impl From<rustls::Error> for Error {
|
|||||||
Error::new(e, ErrorKind::OpenSsl)
|
Error::new(e, ErrorKind::OpenSsl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<lettre::error::Error> for Error {
|
||||||
|
fn from(e: lettre::error::Error) -> Self {
|
||||||
|
Error::new(e, ErrorKind::Smtp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<lettre::transport::smtp::Error> for Error {
|
||||||
|
fn from(e: lettre::transport::smtp::Error) -> Self {
|
||||||
|
Error::new(e, ErrorKind::Smtp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<lettre::address::AddressError> for Error {
|
||||||
|
fn from(e: lettre::address::AddressError) -> Self {
|
||||||
|
Error::new(e, ErrorKind::Smtp)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl From<patch_db::value::Error> for Error {
|
impl From<patch_db::value::Error> for Error {
|
||||||
fn from(value: patch_db::value::Error) -> Self {
|
fn from(value: patch_db::value::Error) -> Self {
|
||||||
match value.kind {
|
match value.kind {
|
||||||
@@ -539,25 +575,24 @@ where
|
|||||||
impl<T, E> ResultExt<T, E> for Result<T, E>
|
impl<T, E> ResultExt<T, E> for Result<T, E>
|
||||||
where
|
where
|
||||||
color_eyre::eyre::Error: From<E>,
|
color_eyre::eyre::Error: From<E>,
|
||||||
|
E: std::fmt::Debug + 'static,
|
||||||
{
|
{
|
||||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
||||||
self.map_err(|e| Error {
|
self.map_err(|e| Error::new(e, kind))
|
||||||
source: e.into(),
|
|
||||||
kind,
|
|
||||||
revision: None,
|
|
||||||
task: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
||||||
self.map_err(|e| {
|
self.map_err(|e| {
|
||||||
let (kind, ctx) = f(&e);
|
let (kind, ctx) = f(&e);
|
||||||
|
let debug = (typeid::of::<E>() == typeid::of::<color_eyre::eyre::Error>())
|
||||||
|
.then(|| eyre!("{ctx}: {e:?}"));
|
||||||
let source = color_eyre::eyre::Error::from(e);
|
let source = color_eyre::eyre::Error::from(e);
|
||||||
let ctx = format!("{}: {}", ctx, source);
|
let with_ctx = format!("{ctx}: {source}");
|
||||||
let source = source.wrap_err(ctx);
|
let source = source.wrap_err(with_ctx);
|
||||||
Error {
|
Error {
|
||||||
kind,
|
kind,
|
||||||
source,
|
source,
|
||||||
|
debug,
|
||||||
revision: None,
|
revision: None,
|
||||||
task: None,
|
task: None,
|
||||||
}
|
}
|
||||||
@@ -578,25 +613,24 @@ where
|
|||||||
}
|
}
|
||||||
impl<T> ResultExt<T, Error> for Result<T, Error> {
|
impl<T> ResultExt<T, Error> for Result<T, Error> {
|
||||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
||||||
self.map_err(|e| Error {
|
self.map_err(|e| Error { kind, ..e })
|
||||||
source: e.source,
|
|
||||||
kind,
|
|
||||||
revision: e.revision,
|
|
||||||
task: e.task,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_ctx<F: FnOnce(&Error) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
fn with_ctx<F: FnOnce(&Error) -> (ErrorKind, D), D: Display>(self, f: F) -> Result<T, Error> {
|
||||||
self.map_err(|e| {
|
self.map_err(|e| {
|
||||||
let (kind, ctx) = f(&e);
|
let (kind, ctx) = f(&e);
|
||||||
let source = e.source;
|
let source = e.source;
|
||||||
let ctx = format!("{}: {}", ctx, source);
|
let with_ctx = format!("{ctx}: {source}");
|
||||||
let source = source.wrap_err(ctx);
|
let source = source.wrap_err(with_ctx);
|
||||||
|
let debug = e.debug.map(|e| {
|
||||||
|
let with_ctx = format!("{ctx}: {e}");
|
||||||
|
e.wrap_err(with_ctx)
|
||||||
|
});
|
||||||
Error {
|
Error {
|
||||||
kind,
|
kind,
|
||||||
source,
|
source,
|
||||||
revision: e.revision,
|
debug,
|
||||||
task: e.task,
|
..e
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,21 @@
|
|||||||
|
|
||||||
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}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
else
|
||||||
|
if [ "$PROFILE" != "debug"]; then
|
||||||
|
>&2 echo "Unknown profile $PROFILE: falling back to debug..."
|
||||||
|
PROFILE=debug
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$ARCH" ]; then
|
if [ -z "$ARCH" ]; then
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
fi
|
fi
|
||||||
@@ -22,15 +34,12 @@ cd ..
|
|||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
RUSTFLAGS=""
|
RUSTFLAGS=""
|
||||||
|
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
if [[ "${ENVIRONMENT}" =~ (^|-)console($|-) ]]; then
|
||||||
RUSTFLAGS="--cfg tokio_unstable"
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$HOME/.cargo/git":/root/.cargo/git -v "$(pwd)":/home/rust/src -w /home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
|
|
||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
rust-musl-builder sh -c "apt-get update && apt-get install -y rsync && cd core && cargo test --release --features=test,$FEATURES --workspace --locked --target=$ARCH-unknown-linux-musl -- --skip export_bindings_ && chown \$UID:\$UID target"
|
rust-zig-builder cargo test --manifest-path=./core/Cargo.toml $BUILD_FLAGS --features=test,$FEATURES --workspace --locked -- --skip export_bindings_
|
||||||
if [ "$(ls -nd core/target | awk '{ print $3 }')" != "$UID" ]; then
|
rust-zig-builder sh -c "chown -R $UID:$UID core/target && chown -R $UID:$UID /root/.cargo"
|
||||||
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
|
||||||
fi
|
|
||||||
@@ -4,18 +4,18 @@ description = "The core of StartOS"
|
|||||||
documentation = "https://docs.rs/start-os"
|
documentation = "https://docs.rs/start-os"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
keywords = [
|
keywords = [
|
||||||
"self-hosted",
|
|
||||||
"raspberry-pi",
|
|
||||||
"privacy",
|
|
||||||
"bitcoin",
|
"bitcoin",
|
||||||
"full-node",
|
"full-node",
|
||||||
"lightning",
|
"lightning",
|
||||||
|
"privacy",
|
||||||
|
"raspberry-pi",
|
||||||
|
"self-hosted",
|
||||||
]
|
]
|
||||||
|
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.10" # VERSION_BUMP
|
version = "0.4.0-alpha.14" # VERSION_BUMP
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "startos"
|
name = "startos"
|
||||||
@@ -42,46 +42,60 @@ name = "tunnelbox"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
cli = ["cli-startd", "cli-registry", "cli-tunnel"]
|
arti = [
|
||||||
|
"arti-client",
|
||||||
|
"models/arti",
|
||||||
|
"safelog",
|
||||||
|
"tor-cell",
|
||||||
|
"tor-hscrypto",
|
||||||
|
"tor-hsservice",
|
||||||
|
"tor-keymgr",
|
||||||
|
"tor-llcrypto",
|
||||||
|
"tor-proto",
|
||||||
|
"tor-rtcompat",
|
||||||
|
]
|
||||||
|
cli = ["cli-registry", "cli-startd", "cli-tunnel"]
|
||||||
cli-container = ["procfs", "pty-process"]
|
cli-container = ["procfs", "pty-process"]
|
||||||
cli-registry = []
|
cli-registry = []
|
||||||
cli-startd = []
|
cli-startd = []
|
||||||
cli-tunnel = []
|
cli-tunnel = []
|
||||||
default = ["cli", "startd", "registry", "cli-container", "tunnel"]
|
console = ["console-subscriber", "tokio/tracing"]
|
||||||
dev = []
|
default = ["cli", "cli-container", "registry", "startd", "tunnel"]
|
||||||
|
dev = ["backtrace-on-stack-overflow"]
|
||||||
docker = []
|
docker = []
|
||||||
registry = []
|
registry = []
|
||||||
startd = ["mail-send"]
|
startd = []
|
||||||
test = []
|
test = []
|
||||||
tunnel = []
|
tunnel = []
|
||||||
unstable = ["console-subscriber", "tokio/tracing"]
|
unstable = ["backtrace-on-stack-overflow"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
aes = { version = "0.7.5", features = ["ctr"] }
|
||||||
arti-client = { version = "0.33", features = [
|
arti-client = { version = "0.33", features = [
|
||||||
"compression",
|
"compression",
|
||||||
|
"ephemeral-keystore",
|
||||||
"experimental-api",
|
"experimental-api",
|
||||||
|
"onion-service-client",
|
||||||
|
"onion-service-service",
|
||||||
"rustls",
|
"rustls",
|
||||||
"static",
|
"static",
|
||||||
"tokio",
|
"tokio",
|
||||||
"ephemeral-keystore",
|
], default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
"onion-service-client",
|
|
||||||
"onion-service-service",
|
|
||||||
], default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
|
||||||
aes = { version = "0.7.5", features = ["ctr"] }
|
|
||||||
async-acme = { version = "0.6.0", git = "https://github.com/dr-bonez/async-acme.git", features = [
|
async-acme = { version = "0.6.0", git = "https://github.com/dr-bonez/async-acme.git", features = [
|
||||||
"use_rustls",
|
"use_rustls",
|
||||||
"use_tokio",
|
"use_tokio",
|
||||||
] }
|
] }
|
||||||
async-compression = { version = "0.4.4", features = [
|
async-compression = { version = "0.4.32", features = [
|
||||||
"gzip",
|
|
||||||
"brotli",
|
"brotli",
|
||||||
|
"gzip",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"zstd",
|
||||||
] }
|
] }
|
||||||
async-stream = "0.3.5"
|
async-stream = "0.3.5"
|
||||||
async-trait = "0.1.74"
|
async-trait = "0.1.74"
|
||||||
axum = { version = "0.8.4", features = ["ws"] }
|
axum = { version = "0.8.4", features = ["ws"] }
|
||||||
|
backtrace-on-stack-overflow = { version = "0.3.0", optional = true }
|
||||||
barrage = "0.2.3"
|
barrage = "0.2.3"
|
||||||
backhand = "0.21.0"
|
|
||||||
base32 = "0.5.0"
|
base32 = "0.5.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
base64ct = "1.6.0"
|
base64ct = "1.6.0"
|
||||||
@@ -96,17 +110,19 @@ console-subscriber = { version = "0.4.1", optional = true }
|
|||||||
const_format = "0.2.34"
|
const_format = "0.2.34"
|
||||||
cookie = "0.18.0"
|
cookie = "0.18.0"
|
||||||
cookie_store = "0.21.0"
|
cookie_store = "0.21.0"
|
||||||
|
curve25519-dalek = "4.1.3"
|
||||||
der = { version = "0.7.9", features = ["derive", "pem"] }
|
der = { version = "0.7.9", features = ["derive", "pem"] }
|
||||||
digest = "0.10.7"
|
digest = "0.10.7"
|
||||||
divrem = "1.0.0"
|
divrem = "1.0.0"
|
||||||
dns-lookup = "2.1.0"
|
dns-lookup = "2.1.0"
|
||||||
ed25519 = { version = "2.2.3", features = ["pkcs8", "pem", "alloc"] }
|
ed25519 = { version = "2.2.3", features = ["alloc", "pem", "pkcs8"] }
|
||||||
ed25519-dalek = { version = "2.2.0", features = [
|
ed25519-dalek = { version = "2.2.0", features = [
|
||||||
|
"digest",
|
||||||
|
"hazmat",
|
||||||
|
"pkcs8",
|
||||||
|
"rand_core",
|
||||||
"serde",
|
"serde",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
"rand_core",
|
|
||||||
"digest",
|
|
||||||
"pkcs8",
|
|
||||||
] }
|
] }
|
||||||
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
|
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
|
||||||
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
exver = { version = "0.2.0", git = "https://github.com/Start9Labs/exver-rs.git", features = [
|
||||||
@@ -123,20 +139,21 @@ hickory-server = "0.25.2"
|
|||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
http = "1.0.0"
|
http = "1.0.0"
|
||||||
http-body-util = "0.1"
|
http-body-util = "0.1"
|
||||||
hyper = { version = "1.5", features = ["server", "http1", "http2"] }
|
hyper = { version = "1.5", features = ["http1", "http2", "server"] }
|
||||||
hyper-util = { version = "0.1.10", features = [
|
hyper-util = { version = "0.1.10", features = [
|
||||||
|
"http1",
|
||||||
|
"http2",
|
||||||
"server",
|
"server",
|
||||||
"server-auto",
|
"server-auto",
|
||||||
"server-graceful",
|
"server-graceful",
|
||||||
"service",
|
"service",
|
||||||
"http1",
|
|
||||||
"http2",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
] }
|
] }
|
||||||
id-pool = { version = "0.2.2", default-features = false, features = [
|
id-pool = { version = "0.2.2", default-features = false, features = [
|
||||||
"serde",
|
"serde",
|
||||||
"u16",
|
"u16",
|
||||||
] }
|
] }
|
||||||
|
iddqd = "0.3.14"
|
||||||
imbl = { version = "6", features = ["serde", "small-chunks"] }
|
imbl = { version = "6", features = ["serde", "small-chunks"] }
|
||||||
imbl-value = { version = "0.4.3", features = ["ts-rs"] }
|
imbl-value = { version = "0.4.3", features = ["ts-rs"] }
|
||||||
include_dir = { version = "0.7.3", features = ["metadata"] }
|
include_dir = { version = "0.7.3", features = ["metadata"] }
|
||||||
@@ -154,10 +171,20 @@ jsonpath_lib = { git = "https://github.com/Start9Labs/jsonpath.git" }
|
|||||||
lazy_async_pool = "0.3.3"
|
lazy_async_pool = "0.3.3"
|
||||||
lazy_format = "2.0"
|
lazy_format = "2.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
lettre = { version = "0.11.18", default-features = false, features = [
|
||||||
|
"aws-lc-rs",
|
||||||
|
"builder",
|
||||||
|
"hostname",
|
||||||
|
"pool",
|
||||||
|
"rustls-platform-verifier",
|
||||||
|
"smtp-transport",
|
||||||
|
"tokio1-rustls",
|
||||||
|
] }
|
||||||
libc = "0.2.149"
|
libc = "0.2.149"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
mio = "1"
|
|
||||||
mbrman = "0.6.0"
|
mbrman = "0.6.0"
|
||||||
|
miette = { version = "7.6.0", features = ["fancy"] }
|
||||||
|
mio = "1"
|
||||||
models = { version = "*", path = "../models" }
|
models = { version = "*", path = "../models" }
|
||||||
new_mime_guess = "4"
|
new_mime_guess = "4"
|
||||||
nix = { version = "0.30.1", features = [
|
nix = { version = "0.30.1", features = [
|
||||||
@@ -171,8 +198,8 @@ nix = { version = "0.30.1", features = [
|
|||||||
] }
|
] }
|
||||||
nom = "8.0.0"
|
nom = "8.0.0"
|
||||||
num = "0.4.1"
|
num = "0.4.1"
|
||||||
num_enum = "0.7.0"
|
|
||||||
num_cpus = "1.16.0"
|
num_cpus = "1.16.0"
|
||||||
|
num_enum = "0.7.0"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
openssh-keys = "0.6.2"
|
openssh-keys = "0.6.2"
|
||||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
openssl = { version = "0.10.57", features = ["vendored"] }
|
||||||
@@ -189,22 +216,22 @@ proptest = "1.3.1"
|
|||||||
proptest-derive = "0.5.0"
|
proptest-derive = "0.5.0"
|
||||||
pty-process = { version = "0.5.1", optional = true }
|
pty-process = { version = "0.5.1", optional = true }
|
||||||
qrcode = "0.14.1"
|
qrcode = "0.14.1"
|
||||||
|
r3bl_tui = "0.7.6"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
reqwest = { version = "0.12.4", features = ["stream", "json", "socks"] }
|
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"
|
||||||
rustyline-async = "0.4.1"
|
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" }
|
|
||||||
semver = { version = "1.0.20", features = ["serde"] }
|
semver = { version = "1.0.20", features = ["serde"] }
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
serde_cbor = { package = "ciborium", version = "0.2.1" }
|
serde_cbor = { package = "ciborium", version = "0.2.1" }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_toml = { package = "toml", version = "0.8.2" }
|
serde_toml = { package = "toml", version = "0.8.2" }
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
serde_with = { version = "3.4.0", features = ["macros", "json"] }
|
serde_with = { version = "3.4.0", features = ["json", "macros"] }
|
||||||
serde_yaml = { package = "serde_yml", version = "0.0.12" }
|
serde_yaml = { package = "serde_yml", version = "0.0.12" }
|
||||||
sha-crypt = "0.5.0"
|
sha-crypt = "0.5.0"
|
||||||
sha2 = "0.10.2"
|
sha2 = "0.10.2"
|
||||||
@@ -212,39 +239,40 @@ shell-words = "1"
|
|||||||
signal-hook = "0.3.17"
|
signal-hook = "0.3.17"
|
||||||
simple-logging = "2.0.2"
|
simple-logging = "2.0.2"
|
||||||
socket2 = { version = "0.6.0", features = ["all"] }
|
socket2 = { version = "0.6.0", features = ["all"] }
|
||||||
socks5-impl = { version = "0.7.2", features = ["server"] }
|
socks5-impl = { version = "0.7.2", features = ["client", "server"] }
|
||||||
sqlx = { version = "0.8.6", features = [
|
sqlx = { version = "0.8.6", features = [
|
||||||
"runtime-tokio-rustls",
|
|
||||||
"postgres",
|
"postgres",
|
||||||
|
"runtime-tokio-rustls",
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
sscanf = "0.4.1"
|
sscanf = "0.4.1"
|
||||||
ssh-key = { version = "0.6.2", features = ["ed25519"] }
|
ssh-key = { version = "0.6.2", features = ["ed25519"] }
|
||||||
tar = "0.4.40"
|
tar = "0.4.40"
|
||||||
termion = "4.0.5"
|
termion = "4.0.5"
|
||||||
thiserror = "2.0.12"
|
|
||||||
textwrap = "0.16.1"
|
textwrap = "0.16.1"
|
||||||
|
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", "sync", "net"] }
|
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"] }
|
||||||
tokio-util = { version = "0.7.9", features = ["io"] }
|
tokio-util = { version = "0.7.9", features = ["io"] }
|
||||||
tor-cell = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
tor-cell = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
tor-hscrypto = { version = "0.33", features = [
|
tor-hscrypto = { version = "0.33", features = [
|
||||||
"full",
|
"full",
|
||||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
tor-hsservice = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
tor-hsservice = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
tor-keymgr = { version = "0.33", features = [
|
tor-keymgr = { version = "0.33", features = [
|
||||||
"ephemeral-keystore",
|
"ephemeral-keystore",
|
||||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
tor-llcrypto = { version = "0.33", features = [
|
tor-llcrypto = { version = "0.33", features = [
|
||||||
"full",
|
"full",
|
||||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
tor-proto = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
tor-proto = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
tor-rtcompat = { version = "0.33", features = [
|
tor-rtcompat = { version = "0.33", features = [
|
||||||
"tokio",
|
|
||||||
"rustls",
|
"rustls",
|
||||||
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
"tokio",
|
||||||
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit", optional = true }
|
||||||
|
torut = "0.2.1"
|
||||||
tower-service = "0.3.3"
|
tower-service = "0.3.3"
|
||||||
tracing = "0.1.39"
|
tracing = "0.1.39"
|
||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
@@ -257,11 +285,10 @@ unix-named-pipe = "0.2.0"
|
|||||||
url = { version = "2.4.1", features = ["serde"] }
|
url = { version = "2.4.1", features = ["serde"] }
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
uuid = { version = "1.4.1", features = ["v4"] }
|
uuid = { version = "1.4.1", features = ["v4"] }
|
||||||
|
visit-rs = "0.1.1"
|
||||||
|
x25519-dalek = { version = "2.0.1", features = ["static_secrets"] }
|
||||||
zbus = "5.1.1"
|
zbus = "5.1.1"
|
||||||
zeroize = "1.6.0"
|
zeroize = "1.6.0"
|
||||||
mail-send = { git = "https://github.com/dr-bonez/mail-send.git", branch = "main", optional = true }
|
|
||||||
rustls = "0.23.20"
|
|
||||||
rustls-pki-types = { version = "1.10.1", features = ["alloc"] }
|
|
||||||
|
|
||||||
[profile.test]
|
[profile.test]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
@@ -107,6 +108,7 @@ impl AccountInfo {
|
|||||||
.map(|tor_key| tor_key.onion_address())
|
.map(|tor_key| tor_key.onion_address())
|
||||||
.collect(),
|
.collect(),
|
||||||
)?;
|
)?;
|
||||||
|
server_info.as_password_hash_mut().ser(&self.password)?;
|
||||||
db.as_private_mut().as_password_mut().ser(&self.password)?;
|
db.as_private_mut().as_password_mut().ser(&self.password)?;
|
||||||
db.as_private_mut()
|
db.as_private_mut()
|
||||||
.as_ssh_privkey_mut()
|
.as_ssh_privkey_mut()
|
||||||
@@ -119,12 +121,20 @@ impl AccountInfo {
|
|||||||
key_store.as_onion_mut().insert_key(tor_key)?;
|
key_store.as_onion_mut().insert_key(tor_key)?;
|
||||||
}
|
}
|
||||||
let cert_store = key_store.as_local_certs_mut();
|
let cert_store = key_store.as_local_certs_mut();
|
||||||
cert_store
|
if cert_store.as_root_cert().de()?.0 != self.root_ca_cert {
|
||||||
.as_root_key_mut()
|
cert_store
|
||||||
.ser(Pem::new_ref(&self.root_ca_key))?;
|
.as_root_key_mut()
|
||||||
cert_store
|
.ser(Pem::new_ref(&self.root_ca_key))?;
|
||||||
.as_root_cert_mut()
|
cert_store
|
||||||
.ser(Pem::new_ref(&self.root_ca_cert))?;
|
.as_root_cert_mut()
|
||||||
|
.ser(Pem::new_ref(&self.root_ca_cert))?;
|
||||||
|
let int_key = crate::net::ssl::generate_key()?;
|
||||||
|
let int_cert =
|
||||||
|
crate::net::ssl::make_int_cert((&self.root_ca_key, &self.root_ca_cert), &int_key)?;
|
||||||
|
cert_store.as_int_key_mut().ser(&Pem(int_key))?;
|
||||||
|
cert_store.as_int_cert_mut().ser(&Pem(int_cert))?;
|
||||||
|
cert_store.as_leaves_mut().ser(&BTreeMap::new())?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use clap::{CommandFactory, FromArgMatches, Parser};
|
|||||||
pub use models::ActionId;
|
pub use models::ActionId;
|
||||||
use models::{PackageId, ReplayId};
|
use models::{PackageId, ReplayId};
|
||||||
use qrcode::QrCode;
|
use qrcode::QrCode;
|
||||||
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
|
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -14,7 +14,7 @@ use crate::db::model::package::TaskSeverity;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
use crate::util::serde::{
|
use crate::util::serde::{
|
||||||
display_serializable, HandlerExtSerde, StdinDeserializable, WithIoFormat,
|
HandlerExtSerde, StdinDeserializable, WithIoFormat, display_serializable,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn action_api<C: Context>() -> ParentHandler<C> {
|
pub fn action_api<C: Context>() -> ParentHandler<C> {
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ pub fn check_password(hash: &str, password: &str) -> Result<(), Error> {
|
|||||||
pub struct LoginParams {
|
pub struct LoginParams {
|
||||||
password: String,
|
password: String,
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_userAgent")] // from Auth middleware
|
#[serde(rename = "__Auth_userAgent")] // from Auth middleware
|
||||||
user_agent: Option<String>,
|
user_agent: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
ephemeral: bool,
|
ephemeral: bool,
|
||||||
@@ -279,7 +279,7 @@ pub async fn login_impl<C: AuthContext>(
|
|||||||
#[command(rename_all = "kebab-case")]
|
#[command(rename_all = "kebab-case")]
|
||||||
pub struct LogoutParams {
|
pub struct LogoutParams {
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_session")] // from Auth middleware
|
#[serde(rename = "__Auth_session")] // from Auth middleware
|
||||||
session: InternedString,
|
session: InternedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +373,7 @@ fn display_sessions(params: WithIoFormat<ListParams>, arg: SessionList) -> Resul
|
|||||||
pub struct ListParams {
|
pub struct ListParams {
|
||||||
#[arg(skip)]
|
#[arg(skip)]
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_session")] // from Auth middleware
|
#[serde(rename = "__Auth_session")] // from Auth middleware
|
||||||
session: Option<InternedString>,
|
session: Option<InternedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,30 +474,19 @@ pub async fn reset_password_impl(
|
|||||||
let old_password = old_password.unwrap_or_default().decrypt(&ctx)?;
|
let old_password = old_password.unwrap_or_default().decrypt(&ctx)?;
|
||||||
let new_password = new_password.unwrap_or_default().decrypt(&ctx)?;
|
let new_password = new_password.unwrap_or_default().decrypt(&ctx)?;
|
||||||
|
|
||||||
let mut account = ctx.account.write().await;
|
let account = ctx.account.mutate(|account| {
|
||||||
if !argon2::verify_encoded(&account.password, old_password.as_bytes())
|
if !argon2::verify_encoded(&account.password, old_password.as_bytes())
|
||||||
.with_kind(crate::ErrorKind::IncorrectPassword)?
|
.with_kind(crate::ErrorKind::IncorrectPassword)?
|
||||||
{
|
{
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("Incorrect Password"),
|
eyre!("Incorrect Password"),
|
||||||
crate::ErrorKind::IncorrectPassword,
|
crate::ErrorKind::IncorrectPassword,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
account.set_password(&new_password)?;
|
account.set_password(&new_password)?;
|
||||||
let account_password = &account.password;
|
Ok(account.clone())
|
||||||
let account = account.clone();
|
})?;
|
||||||
ctx.db
|
ctx.db.mutate(|d| account.save(d)).await.result
|
||||||
.mutate(|d| {
|
|
||||||
d.as_public_mut()
|
|
||||||
.as_server_info_mut()
|
|
||||||
.as_password_hash_mut()
|
|
||||||
.ser(account_password)?;
|
|
||||||
account.save(d)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ async fn perform_backup(
|
|||||||
.with_kind(ErrorKind::Filesystem)?;
|
.with_kind(ErrorKind::Filesystem)?;
|
||||||
os_backup_file
|
os_backup_file
|
||||||
.write_all(&IoFormat::Json.to_vec(&OsBackup {
|
.write_all(&IoFormat::Json.to_vec(&OsBackup {
|
||||||
account: ctx.account.read().await.clone(),
|
account: ctx.account.peek(|a| a.clone()),
|
||||||
ui,
|
ui,
|
||||||
})?)
|
})?)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -342,7 +342,7 @@ async fn perform_backup(
|
|||||||
let timestamp = Utc::now();
|
let timestamp = Utc::now();
|
||||||
|
|
||||||
backup_guard.unencrypted_metadata.version = crate::version::Current::default().semver().into();
|
backup_guard.unencrypted_metadata.version = crate::version::Current::default().semver().into();
|
||||||
backup_guard.unencrypted_metadata.hostname = ctx.account.read().await.hostname.clone();
|
backup_guard.unencrypted_metadata.hostname = ctx.account.peek(|a| a.hostname.clone());
|
||||||
backup_guard.unencrypted_metadata.timestamp = timestamp.clone();
|
backup_guard.unencrypted_metadata.timestamp = timestamp.clone();
|
||||||
backup_guard.metadata.version = crate::version::Current::default().semver().into();
|
backup_guard.metadata.version = crate::version::Current::default().semver().into();
|
||||||
backup_guard.metadata.timestamp = Some(timestamp);
|
backup_guard.metadata.timestamp = Some(timestamp);
|
||||||
|
|||||||
@@ -11,14 +11,17 @@ use crate::context::config::ClientConfig;
|
|||||||
use crate::net::web_server::{Acceptor, WebServer};
|
use crate::net::web_server::{Acceptor, WebServer};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::context::{RegistryConfig, RegistryContext};
|
use crate::registry::context::{RegistryConfig, RegistryContext};
|
||||||
|
use crate::registry::registry_router;
|
||||||
use crate::util::logger::LOGGER;
|
use crate::util::logger::LOGGER;
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn inner_main(config: &RegistryConfig) -> Result<(), Error> {
|
async fn inner_main(config: &RegistryConfig) -> Result<(), Error> {
|
||||||
let server = async {
|
let server = async {
|
||||||
let ctx = RegistryContext::init(config).await?;
|
let ctx = RegistryContext::init(config).await?;
|
||||||
let mut server = WebServer::new(Acceptor::bind([ctx.listen]).await?);
|
let server = WebServer::new(
|
||||||
server.serve_registry(ctx.clone());
|
Acceptor::bind([ctx.listen]).await?,
|
||||||
|
registry_router(ctx.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
let mut shutdown_recv = ctx.shutdown.subscribe();
|
let mut shutdown_recv = ctx.shutdown.subscribe();
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
|||||||
|
|
||||||
if let Err(e) = CliApp::new(
|
if let Err(e) = CliApp::new(
|
||||||
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||||
crate::expanded_api(),
|
crate::main_api(),
|
||||||
)
|
)
|
||||||
.run(args)
|
.run(args)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ use crate::disk::fsck::RepairStrategy;
|
|||||||
use crate::disk::main::DEFAULT_PASSWORD;
|
use crate::disk::main::DEFAULT_PASSWORD;
|
||||||
use crate::firmware::{check_for_firmware_update, update_firmware};
|
use crate::firmware::{check_for_firmware_update, update_firmware};
|
||||||
use crate::init::{InitPhases, STANDBY_MODE_PATH};
|
use crate::init::{InitPhases, STANDBY_MODE_PATH};
|
||||||
use crate::net::web_server::{UpgradableListener, WebServer};
|
use crate::net::gateway::UpgradableListener;
|
||||||
|
use crate::net::web_server::WebServer;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::FullProgressTracker;
|
use crate::progress::FullProgressTracker;
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
@@ -37,7 +38,7 @@ async fn setup_or_init(
|
|||||||
let mut update_phase = handle.add_phase("Updating Firmware".into(), Some(10));
|
let mut update_phase = handle.add_phase("Updating Firmware".into(), Some(10));
|
||||||
let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
|
let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
|
||||||
|
|
||||||
server.serve_init(init_ctx);
|
server.serve_ui_for(init_ctx);
|
||||||
|
|
||||||
update_phase.start();
|
update_phase.start();
|
||||||
if let Err(e) = update_firmware(firmware).await {
|
if let Err(e) = update_firmware(firmware).await {
|
||||||
@@ -93,7 +94,7 @@ async fn setup_or_init(
|
|||||||
|
|
||||||
let ctx = InstallContext::init().await?;
|
let ctx = InstallContext::init().await?;
|
||||||
|
|
||||||
server.serve_install(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
|
|
||||||
ctx.shutdown
|
ctx.shutdown
|
||||||
.subscribe()
|
.subscribe()
|
||||||
@@ -113,7 +114,7 @@ async fn setup_or_init(
|
|||||||
{
|
{
|
||||||
let ctx = SetupContext::init(server, config)?;
|
let ctx = SetupContext::init(server, config)?;
|
||||||
|
|
||||||
server.serve_setup(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
|
|
||||||
let mut shutdown = ctx.shutdown.subscribe();
|
let mut shutdown = ctx.shutdown.subscribe();
|
||||||
if let Some(shutdown) = shutdown.recv().await.expect("context dropped") {
|
if let Some(shutdown) = shutdown.recv().await.expect("context dropped") {
|
||||||
@@ -149,7 +150,7 @@ async fn setup_or_init(
|
|||||||
let init_phases = InitPhases::new(&handle);
|
let init_phases = InitPhases::new(&handle);
|
||||||
let rpc_ctx_phases = InitRpcContextPhases::new(&handle);
|
let rpc_ctx_phases = InitRpcContextPhases::new(&handle);
|
||||||
|
|
||||||
server.serve_init(init_ctx);
|
server.serve_ui_for(init_ctx);
|
||||||
|
|
||||||
async {
|
async {
|
||||||
disk_phase.start();
|
disk_phase.start();
|
||||||
@@ -247,7 +248,7 @@ pub async fn main(
|
|||||||
e,
|
e,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
server.serve_diagnostic(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
|
|
||||||
let shutdown = ctx.shutdown.subscribe().recv().await.unwrap();
|
let shutdown = ctx.shutdown.subscribe().recv().await.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ use tracing::instrument;
|
|||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::context::rpc::InitRpcContextPhases;
|
use crate::context::rpc::InitRpcContextPhases;
|
||||||
use crate::context::{DiagnosticContext, InitContext, RpcContext};
|
use crate::context::{DiagnosticContext, InitContext, RpcContext};
|
||||||
use crate::net::gateway::SelfContainedNetworkInterfaceListener;
|
use crate::net::gateway::{BindTcp, SelfContainedNetworkInterfaceListener, UpgradableListener};
|
||||||
use crate::net::web_server::{Acceptor, UpgradableListener, WebServer};
|
use crate::net::static_server::refresher;
|
||||||
|
use crate::net::web_server::{Acceptor, WebServer};
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
use crate::system::launch_metrics_task;
|
use crate::system::launch_metrics_task;
|
||||||
use crate::util::io::append_file;
|
use crate::util::io::append_file;
|
||||||
@@ -38,7 +39,7 @@ async fn inner_main(
|
|||||||
};
|
};
|
||||||
tokio::fs::write("/run/startos/initialized", "").await?;
|
tokio::fs::write("/run/startos/initialized", "").await?;
|
||||||
|
|
||||||
server.serve_main(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
LOGGER.set_logfile(None);
|
LOGGER.set_logfile(None);
|
||||||
handle.complete();
|
handle.complete();
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ async fn inner_main(
|
|||||||
let init_ctx = InitContext::init(config).await?;
|
let init_ctx = InitContext::init(config).await?;
|
||||||
let handle = init_ctx.progress.clone();
|
let handle = init_ctx.progress.clone();
|
||||||
let rpc_ctx_phases = InitRpcContextPhases::new(&handle);
|
let rpc_ctx_phases = InitRpcContextPhases::new(&handle);
|
||||||
server.serve_init(init_ctx);
|
server.serve_ui_for(init_ctx);
|
||||||
|
|
||||||
let ctx = RpcContext::init(
|
let ctx = RpcContext::init(
|
||||||
&server.acceptor_setter(),
|
&server.acceptor_setter(),
|
||||||
@@ -63,14 +64,14 @@ async fn inner_main(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
server.serve_main(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
handle.complete();
|
handle.complete();
|
||||||
|
|
||||||
ctx
|
ctx
|
||||||
};
|
};
|
||||||
|
|
||||||
let (rpc_ctx, shutdown) = async {
|
let (rpc_ctx, shutdown) = async {
|
||||||
crate::hostname::sync_hostname(&rpc_ctx.account.read().await.hostname).await?;
|
crate::hostname::sync_hostname(&rpc_ctx.account.peek(|a| a.hostname.clone())).await?;
|
||||||
|
|
||||||
let mut shutdown_recv = rpc_ctx.shutdown.subscribe();
|
let mut shutdown_recv = rpc_ctx.shutdown.subscribe();
|
||||||
|
|
||||||
@@ -132,8 +133,6 @@ async fn inner_main(
|
|||||||
.await?;
|
.await?;
|
||||||
rpc_ctx.shutdown().await?;
|
rpc_ctx.shutdown().await?;
|
||||||
|
|
||||||
tracing::info!("RPC Context is dropped");
|
|
||||||
|
|
||||||
Ok(shutdown)
|
Ok(shutdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,9 +148,10 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
|||||||
.build()
|
.build()
|
||||||
.expect("failed to initialize runtime");
|
.expect("failed to initialize runtime");
|
||||||
let res = rt.block_on(async {
|
let res = rt.block_on(async {
|
||||||
let mut server = WebServer::new(Acceptor::bind_upgradable(
|
let mut server = WebServer::new(
|
||||||
SelfContainedNetworkInterfaceListener::bind(80),
|
Acceptor::bind_upgradable(SelfContainedNetworkInterfaceListener::bind(BindTcp, 80)),
|
||||||
));
|
refresher(),
|
||||||
|
);
|
||||||
match inner_main(&mut server, &config).await {
|
match inner_main(&mut server, &config).await {
|
||||||
Ok(a) => {
|
Ok(a) => {
|
||||||
server.shutdown().await;
|
server.shutdown().await;
|
||||||
@@ -179,7 +179,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
|||||||
e,
|
e,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
server.serve_diagnostic(ctx.clone());
|
server.serve_ui_for(ctx.clone());
|
||||||
|
|
||||||
let mut shutdown = ctx.shutdown.subscribe();
|
let mut shutdown = ctx.shutdown.subscribe();
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,110 @@
|
|||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
use helpers::NonDetachingJoinHandle;
|
||||||
use rpc_toolkit::CliApp;
|
use rpc_toolkit::CliApp;
|
||||||
use tokio::signal::unix::signal;
|
use tokio::signal::unix::signal;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
use visit_rs::Visit;
|
||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::context::config::ClientConfig;
|
use crate::context::config::ClientConfig;
|
||||||
use crate::net::web_server::{Acceptor, WebServer};
|
use crate::net::gateway::{Bind, BindTcp};
|
||||||
|
use crate::net::tls::TlsListener;
|
||||||
|
use crate::net::web_server::{Accept, Acceptor, MetadataVisitor, WebServer};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::tunnel::context::{TunnelConfig, TunnelContext};
|
use crate::tunnel::context::{TunnelConfig, TunnelContext};
|
||||||
|
use crate::tunnel::tunnel_router;
|
||||||
|
use crate::tunnel::web::TunnelCertHandler;
|
||||||
use crate::util::logger::LOGGER;
|
use crate::util::logger::LOGGER;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
enum WebserverListener {
|
||||||
|
Http,
|
||||||
|
Https(SocketAddr),
|
||||||
|
}
|
||||||
|
impl<V: MetadataVisitor> Visit<V> for WebserverListener {
|
||||||
|
fn visit(&self, visitor: &mut V) -> <V as visit_rs::Visitor>::Result {
|
||||||
|
visitor.visit(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
|
async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
|
||||||
let server = async {
|
let server = async {
|
||||||
let ctx = TunnelContext::init(config).await?;
|
let ctx = TunnelContext::init(config).await?;
|
||||||
let mut server = WebServer::new(Acceptor::bind([ctx.listen]).await?);
|
let listen = ctx.listen;
|
||||||
server.serve_tunnel(ctx.clone());
|
let server = WebServer::new(
|
||||||
|
Acceptor::bind_map_dyn([(WebserverListener::Http, listen)]).await?,
|
||||||
|
tunnel_router(ctx.clone()),
|
||||||
|
);
|
||||||
|
let acceptor_setter = server.acceptor_setter();
|
||||||
|
let https_db = ctx.db.clone();
|
||||||
|
let https_thread: NonDetachingJoinHandle<()> = tokio::spawn(async move {
|
||||||
|
let mut sub = https_db.subscribe("/webserver".parse().unwrap()).await;
|
||||||
|
while {
|
||||||
|
while let Err(e) = async {
|
||||||
|
let webserver = https_db.peek().await.into_webserver();
|
||||||
|
if webserver.as_enabled().de()? {
|
||||||
|
let addr = webserver.as_listen().de()?.or_not_found("listen address")?;
|
||||||
|
acceptor_setter.send_if_modified(|a| {
|
||||||
|
let key = WebserverListener::Https(addr);
|
||||||
|
if !a.contains_key(&key) {
|
||||||
|
match (|| {
|
||||||
|
Ok::<_, Error>(TlsListener::new(
|
||||||
|
BindTcp.bind(addr)?,
|
||||||
|
TunnelCertHandler {
|
||||||
|
db: https_db.clone(),
|
||||||
|
crypto_provider: Arc::new(tokio_rustls::rustls::crypto::ring::default_provider()),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
})() {
|
||||||
|
Ok(l) => {
|
||||||
|
a.retain(|k, _| *k == WebserverListener::Http);
|
||||||
|
a.insert(key, l.into_dyn());
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("error adding ssl listener: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
acceptor_setter.send_if_modified(|a| {
|
||||||
|
let before = a.len();
|
||||||
|
a.retain(|k, _| *k == WebserverListener::Http);
|
||||||
|
a.len() != before
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<_, Error>(())
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("error updating webserver bind: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
|
}
|
||||||
|
sub.recv().await.is_some()
|
||||||
|
} {}
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
let mut shutdown_recv = ctx.shutdown.subscribe();
|
let mut shutdown_recv = ctx.shutdown.subscribe();
|
||||||
|
|
||||||
let sig_handler_ctx = ctx;
|
let sig_handler_ctx = ctx;
|
||||||
let sig_handler = tokio::spawn(async move {
|
let sig_handler: NonDetachingJoinHandle<()> = tokio::spawn(async move {
|
||||||
use tokio::signal::unix::SignalKind;
|
use tokio::signal::unix::SignalKind;
|
||||||
futures::future::select_all(
|
futures::future::select_all(
|
||||||
[
|
[
|
||||||
@@ -48,14 +129,16 @@ async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
|
|||||||
.send(())
|
.send(())
|
||||||
.map_err(|_| ())
|
.map_err(|_| ())
|
||||||
.expect("send shutdown signal");
|
.expect("send shutdown signal");
|
||||||
});
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
shutdown_recv
|
shutdown_recv
|
||||||
.recv()
|
.recv()
|
||||||
.await
|
.await
|
||||||
.with_kind(crate::ErrorKind::Unknown)?;
|
.with_kind(crate::ErrorKind::Unknown)?;
|
||||||
|
|
||||||
sig_handler.abort();
|
sig_handler.wait_for_abort().await.with_kind(ErrorKind::Unknown)?;
|
||||||
|
https_thread.wait_for_abort().await.with_kind(ErrorKind::Unknown)?;
|
||||||
|
|
||||||
Ok::<_, Error>(server)
|
Ok::<_, Error>(server)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use cookie::{Cookie, Expiration, SameSite};
|
use cookie::{Cookie, Expiration, SameSite};
|
||||||
use cookie_store::CookieStore;
|
use cookie_store::CookieStore;
|
||||||
|
use http::HeaderMap;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
@@ -26,7 +27,7 @@ use crate::developer::{OS_DEVELOPER_KEY_PATH, default_developer_key_path};
|
|||||||
use crate::middleware::auth::AuthContext;
|
use crate::middleware::auth::AuthContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
use crate::tunnel::context::TunnelContext;
|
use crate::util::io::read_file_to_string;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CliContextSeed {
|
pub struct CliContextSeed {
|
||||||
@@ -159,7 +160,7 @@ impl CliContext {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let pair = <ed25519::KeypairBytes as ed25519::pkcs8::DecodePrivateKey>::from_pkcs8_pem(
|
let pair = <ed25519::KeypairBytes as ed25519::pkcs8::DecodePrivateKey>::from_pkcs8_pem(
|
||||||
&std::fs::read_to_string(&self.developer_key_path)?,
|
&std::fs::read_to_string(path)?,
|
||||||
)
|
)
|
||||||
.with_kind(crate::ErrorKind::Pem)?;
|
.with_kind(crate::ErrorKind::Pem)?;
|
||||||
let secret = ed25519_dalek::SecretKey::try_from(&pair.secret_key[..]).map_err(|_| {
|
let secret = ed25519_dalek::SecretKey::try_from(&pair.secret_key[..]).map_err(|_| {
|
||||||
@@ -171,7 +172,7 @@ impl CliContext {
|
|||||||
return Ok(secret.into())
|
return Ok(secret.into())
|
||||||
}
|
}
|
||||||
Err(Error::new(
|
Err(Error::new(
|
||||||
eyre!("Developer Key does not exist! Please run `start-cli init` before running this command."),
|
eyre!("Developer Key does not exist! Please run `start-cli init-key` before running this command."),
|
||||||
crate::ErrorKind::Uninitialized
|
crate::ErrorKind::Uninitialized
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
@@ -233,23 +234,28 @@ impl CliContext {
|
|||||||
&self,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
params: Value,
|
params: Value,
|
||||||
) -> Result<Value, RpcError>
|
) -> Result<Value, Error>
|
||||||
where
|
where
|
||||||
Self: CallRemote<RemoteContext>,
|
Self: CallRemote<RemoteContext>,
|
||||||
{
|
{
|
||||||
<Self as CallRemote<RemoteContext, Empty>>::call_remote(&self, method, params, Empty {})
|
<Self as CallRemote<RemoteContext, Empty>>::call_remote(&self, method, params, Empty {})
|
||||||
.await
|
.await
|
||||||
|
.map_err(Error::from)
|
||||||
|
.with_ctx(|e| (e.kind, method))
|
||||||
}
|
}
|
||||||
pub async fn call_remote_with<RemoteContext, T>(
|
pub async fn call_remote_with<RemoteContext, T>(
|
||||||
&self,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
params: Value,
|
params: Value,
|
||||||
extra: T,
|
extra: T,
|
||||||
) -> Result<Value, RpcError>
|
) -> Result<Value, Error>
|
||||||
where
|
where
|
||||||
Self: CallRemote<RemoteContext, T>,
|
Self: CallRemote<RemoteContext, T>,
|
||||||
{
|
{
|
||||||
<Self as CallRemote<RemoteContext, T>>::call_remote(&self, method, params, extra).await
|
<Self as CallRemote<RemoteContext, T>>::call_remote(&self, method, params, extra)
|
||||||
|
.await
|
||||||
|
.map_err(Error::from)
|
||||||
|
.with_ctx(|e| (e.kind, method))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl AsRef<Jwk> for CliContext {
|
impl AsRef<Jwk> for CliContext {
|
||||||
@@ -279,9 +285,15 @@ impl Context for CliContext {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AsRef<Client> for CliContext {
|
||||||
|
fn as_ref(&self) -> &Client {
|
||||||
|
&self.client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CallRemote<RpcContext> for CliContext {
|
impl CallRemote<RpcContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
if let Ok(local) = std::fs::read_to_string(RpcContext::LOCAL_AUTH_COOKIE_PATH) {
|
if let Ok(local) = read_file_to_string(RpcContext::LOCAL_AUTH_COOKIE_PATH).await {
|
||||||
self.cookie_store
|
self.cookie_store
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -298,7 +310,8 @@ impl CallRemote<RpcContext> for CliContext {
|
|||||||
crate::middleware::signature::call_remote(
|
crate::middleware::signature::call_remote(
|
||||||
self,
|
self,
|
||||||
self.rpc_url.clone(),
|
self.rpc_url.clone(),
|
||||||
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
HeaderMap::new(),
|
||||||
|
self.rpc_url.host_str(),
|
||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
)
|
)
|
||||||
@@ -307,24 +320,11 @@ impl CallRemote<RpcContext> for CliContext {
|
|||||||
}
|
}
|
||||||
impl CallRemote<DiagnosticContext> for CliContext {
|
impl CallRemote<DiagnosticContext> for CliContext {
|
||||||
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
async fn call_remote(&self, method: &str, params: Value, _: Empty) -> Result<Value, RpcError> {
|
||||||
if let Ok(local) = std::fs::read_to_string(TunnelContext::LOCAL_AUTH_COOKIE_PATH) {
|
|
||||||
self.cookie_store
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert_raw(
|
|
||||||
&Cookie::build(("local", local))
|
|
||||||
.domain("localhost")
|
|
||||||
.expires(Expiration::Session)
|
|
||||||
.same_site(SameSite::Strict)
|
|
||||||
.build(),
|
|
||||||
&"http://localhost".parse()?,
|
|
||||||
)
|
|
||||||
.with_kind(crate::ErrorKind::Network)?;
|
|
||||||
}
|
|
||||||
crate::middleware::signature::call_remote(
|
crate::middleware::signature::call_remote(
|
||||||
self,
|
self,
|
||||||
self.rpc_url.clone(),
|
self.rpc_url.clone(),
|
||||||
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
HeaderMap::new(),
|
||||||
|
self.rpc_url.host_str(),
|
||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
)
|
)
|
||||||
@@ -336,7 +336,8 @@ impl CallRemote<InitContext> for CliContext {
|
|||||||
crate::middleware::signature::call_remote(
|
crate::middleware::signature::call_remote(
|
||||||
self,
|
self,
|
||||||
self.rpc_url.clone(),
|
self.rpc_url.clone(),
|
||||||
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
HeaderMap::new(),
|
||||||
|
self.rpc_url.host_str(),
|
||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
)
|
)
|
||||||
@@ -348,7 +349,8 @@ impl CallRemote<SetupContext> for CliContext {
|
|||||||
crate::middleware::signature::call_remote(
|
crate::middleware::signature::call_remote(
|
||||||
self,
|
self,
|
||||||
self.rpc_url.clone(),
|
self.rpc_url.clone(),
|
||||||
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
HeaderMap::new(),
|
||||||
|
self.rpc_url.host_str(),
|
||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
)
|
)
|
||||||
@@ -360,22 +362,11 @@ impl CallRemote<InstallContext> for CliContext {
|
|||||||
crate::middleware::signature::call_remote(
|
crate::middleware::signature::call_remote(
|
||||||
self,
|
self,
|
||||||
self.rpc_url.clone(),
|
self.rpc_url.clone(),
|
||||||
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
HeaderMap::new(),
|
||||||
|
self.rpc_url.host_str(),
|
||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test() {
|
|
||||||
let ctx = CliContext::init(ClientConfig::default()).unwrap();
|
|
||||||
ctx.runtime().unwrap().block_on(async {
|
|
||||||
reqwest::Client::new()
|
|
||||||
.get("http://example.com")
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ use tokio::sync::broadcast::Sender;
|
|||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::progress::FullProgressTracker;
|
use crate::progress::FullProgressTracker;
|
||||||
use crate::rpc_continuations::RpcContinuations;
|
use crate::rpc_continuations::RpcContinuations;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
pub struct InitContextSeed {
|
pub struct InitContextSeed {
|
||||||
pub config: ServerConfig,
|
pub config: ServerConfig,
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use chrono::{TimeDelta, Utc};
|
use chrono::{TimeDelta, Utc};
|
||||||
@@ -18,36 +17,37 @@ use models::{ActionId, PackageId};
|
|||||||
use reqwest::{Client, Proxy};
|
use reqwest::{Client, Proxy};
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{CallRemote, Context, Empty};
|
use rpc_toolkit::{CallRemote, Context, Empty};
|
||||||
use tokio::sync::{broadcast, oneshot, watch, RwLock};
|
use tokio::sync::{RwLock, broadcast, oneshot, watch};
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::setup::CURRENT_SECRET;
|
use super::setup::CURRENT_SECRET;
|
||||||
|
use crate::DATA_DIR;
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::auth::Sessions;
|
use crate::auth::Sessions;
|
||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::db::model::package::TaskSeverity;
|
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
|
use crate::db::model::package::TaskSeverity;
|
||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
use crate::init::{check_time_is_synchronized, InitResult};
|
use crate::init::{InitResult, check_time_is_synchronized};
|
||||||
use crate::install::PKG_ARCHIVE_DIR;
|
use crate::install::PKG_ARCHIVE_DIR;
|
||||||
use crate::lxc::LxcManager;
|
use crate::lxc::LxcManager;
|
||||||
|
use crate::net::gateway::UpgradableListener;
|
||||||
use crate::net::net_controller::{NetController, NetService};
|
use crate::net::net_controller::{NetController, NetService};
|
||||||
use crate::net::socks::DEFAULT_SOCKS_LISTEN;
|
use crate::net::socks::DEFAULT_SOCKS_LISTEN;
|
||||||
use crate::net::utils::{find_eth_iface, find_wifi_iface};
|
use crate::net::utils::{find_eth_iface, find_wifi_iface};
|
||||||
use crate::net::web_server::{UpgradableListener, WebServerAcceptorSetter};
|
use crate::net::web_server::WebServerAcceptorSetter;
|
||||||
use crate::net::wifi::WpaCli;
|
use crate::net::wifi::WpaCli;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle};
|
use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle};
|
||||||
use crate::rpc_continuations::{Guid, OpenAuthedContinuations, RpcContinuations};
|
use crate::rpc_continuations::{Guid, OpenAuthedContinuations, RpcContinuations};
|
||||||
|
use crate::service::ServiceMap;
|
||||||
use crate::service::action::update_tasks;
|
use crate::service::action::update_tasks;
|
||||||
use crate::service::effects::callbacks::ServiceCallbacks;
|
use crate::service::effects::callbacks::ServiceCallbacks;
|
||||||
use crate::service::ServiceMap;
|
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
use crate::util::io::delete_file;
|
use crate::util::io::delete_file;
|
||||||
use crate::util::lshw::LshwDevice;
|
use crate::util::lshw::LshwDevice;
|
||||||
use crate::util::sync::{SyncMutex, Watch};
|
use crate::util::sync::{SyncMutex, SyncRwLock, Watch};
|
||||||
use crate::{DATA_DIR, HOST_IP};
|
|
||||||
|
|
||||||
pub struct RpcContextSeed {
|
pub struct RpcContextSeed {
|
||||||
is_closed: AtomicBool,
|
is_closed: AtomicBool,
|
||||||
@@ -58,7 +58,7 @@ pub struct RpcContextSeed {
|
|||||||
pub ephemeral_sessions: SyncMutex<Sessions>,
|
pub ephemeral_sessions: SyncMutex<Sessions>,
|
||||||
pub db: TypedPatchDb<Database>,
|
pub db: TypedPatchDb<Database>,
|
||||||
pub sync_db: watch::Sender<u64>,
|
pub sync_db: watch::Sender<u64>,
|
||||||
pub account: RwLock<AccountInfo>,
|
pub account: SyncRwLock<AccountInfo>,
|
||||||
pub net_controller: Arc<NetController>,
|
pub net_controller: Arc<NetController>,
|
||||||
pub os_net_service: NetService,
|
pub os_net_service: NetService,
|
||||||
pub s9pk_arch: Option<&'static str>,
|
pub s9pk_arch: Option<&'static str>,
|
||||||
@@ -76,6 +76,11 @@ pub struct RpcContextSeed {
|
|||||||
pub start_time: Instant,
|
pub start_time: Instant,
|
||||||
pub crons: SyncMutex<BTreeMap<Guid, NonDetachingJoinHandle<()>>>,
|
pub crons: SyncMutex<BTreeMap<Guid, NonDetachingJoinHandle<()>>>,
|
||||||
}
|
}
|
||||||
|
impl Drop for RpcContextSeed {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
tracing::info!("RpcContext is dropped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Hardware {
|
pub struct Hardware {
|
||||||
pub devices: Vec<LshwDevice>,
|
pub devices: Vec<LshwDevice>,
|
||||||
@@ -220,7 +225,7 @@ impl RpcContext {
|
|||||||
ephemeral_sessions: SyncMutex::new(Sessions::new()),
|
ephemeral_sessions: SyncMutex::new(Sessions::new()),
|
||||||
sync_db: watch::Sender::new(db.sequence().await),
|
sync_db: watch::Sender::new(db.sequence().await),
|
||||||
db,
|
db,
|
||||||
account: RwLock::new(account),
|
account: SyncRwLock::new(account),
|
||||||
callbacks: net_controller.callbacks.clone(),
|
callbacks: net_controller.callbacks.clone(),
|
||||||
net_controller,
|
net_controller,
|
||||||
os_net_service,
|
os_net_service,
|
||||||
@@ -269,7 +274,7 @@ impl RpcContext {
|
|||||||
self.crons.mutate(|c| std::mem::take(c));
|
self.crons.mutate(|c| std::mem::take(c));
|
||||||
self.services.shutdown_all().await?;
|
self.services.shutdown_all().await?;
|
||||||
self.is_closed.store(true, Ordering::SeqCst);
|
self.is_closed.store(true, Ordering::SeqCst);
|
||||||
tracing::info!("RPC Context is shutdown");
|
tracing::info!("RpcContext is shutdown");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,6 +483,11 @@ impl RpcContext {
|
|||||||
<Self as CallRemote<RemoteContext, T>>::call_remote(&self, method, params, extra).await
|
<Self as CallRemote<RemoteContext, T>>::call_remote(&self, method, params, extra).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AsRef<Client> for RpcContext {
|
||||||
|
fn as_ref(&self) -> &Client {
|
||||||
|
&self.client
|
||||||
|
}
|
||||||
|
}
|
||||||
impl AsRef<Jwk> for RpcContext {
|
impl AsRef<Jwk> for RpcContext {
|
||||||
fn as_ref(&self) -> &Jwk {
|
fn as_ref(&self) -> &Jwk {
|
||||||
&CURRENT_SECRET
|
&CURRENT_SECRET
|
||||||
|
|||||||
@@ -10,24 +10,25 @@ use josekit::jwk::Jwk;
|
|||||||
use patch_db::PatchDb;
|
use patch_db::PatchDb;
|
||||||
use rpc_toolkit::Context;
|
use rpc_toolkit::Context;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::broadcast::Sender;
|
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
use tokio::sync::broadcast::Sender;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use crate::MAIN_DATA;
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::context::config::ServerConfig;
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
|
use crate::context::config::ServerConfig;
|
||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::Hostname;
|
||||||
use crate::net::web_server::{UpgradableListener, WebServer, WebServerAcceptorSetter};
|
use crate::net::gateway::UpgradableListener;
|
||||||
|
use crate::net::web_server::{WebServer, WebServerAcceptorSetter};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::FullProgressTracker;
|
use crate::progress::FullProgressTracker;
|
||||||
use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations};
|
use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations};
|
||||||
use crate::setup::SetupProgress;
|
use crate::setup::SetupProgress;
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
use crate::util::net::WebSocketExt;
|
use crate::util::net::WebSocketExt;
|
||||||
use crate::MAIN_DATA;
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref CURRENT_SECRET: Jwk = Jwk::generate_ec_key(josekit::jwk::alg::ec::EcCurve::P256).unwrap_or_else(|e| {
|
pub static ref CURRENT_SECRET: Jwk = Jwk::generate_ec_key(josekit::jwk::alg::ec::EcCurve::P256).unwrap_or_else(|e| {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
|
||||||
|
use std::panic::UnwindSafe;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -29,6 +30,26 @@ lazy_static::lazy_static! {
|
|||||||
static ref PUBLIC: JsonPointer = "/public".parse().unwrap();
|
static ref PUBLIC: JsonPointer = "/public".parse().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait DbAccess<T>: Sized {
|
||||||
|
fn access<'a>(db: &'a Model<Self>) -> &'a Model<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DbAccessMut<T>: DbAccess<T> {
|
||||||
|
fn access_mut<'a>(db: &'a mut Model<Self>) -> &'a mut Model<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DbAccessByKey<T>: Sized {
|
||||||
|
type Key<'a>;
|
||||||
|
fn access_by_key<'a>(db: &'a Model<Self>, key: Self::Key<'_>) -> Option<&'a Model<T>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DbAccessMutByKey<T>: DbAccessByKey<T> {
|
||||||
|
fn access_mut_by_key<'a>(
|
||||||
|
db: &'a mut Model<Self>,
|
||||||
|
key: Self::Key<'_>,
|
||||||
|
) -> Option<&'a mut Model<T>>;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn db<C: Context>() -> ParentHandler<C> {
|
pub fn db<C: Context>() -> ParentHandler<C> {
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
@@ -127,7 +148,7 @@ pub struct SubscribeParams {
|
|||||||
#[ts(type = "string | null")]
|
#[ts(type = "string | null")]
|
||||||
pointer: Option<JsonPointer>,
|
pointer: Option<JsonPointer>,
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_session")]
|
#[serde(rename = "__Auth_session")]
|
||||||
session: Option<InternedString>,
|
session: Option<InternedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use exver::{Version, VersionRange};
|
use exver::{Version, VersionRange};
|
||||||
@@ -8,7 +9,6 @@ use imbl_value::InternedString;
|
|||||||
use ipnet::IpNet;
|
use ipnet::IpNet;
|
||||||
use isocountry::CountryCode;
|
use isocountry::CountryCode;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use models::{GatewayId, PackageId};
|
use models::{GatewayId, PackageId};
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
use patch_db::{HasModel, Value};
|
use patch_db::{HasModel, Value};
|
||||||
@@ -16,11 +16,12 @@ use serde::{Deserialize, Serialize};
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
|
use crate::db::DbAccessByKey;
|
||||||
|
use crate::db::model::Database;
|
||||||
use crate::db::model::package::AllPackageData;
|
use crate::db::model::package::AllPackageData;
|
||||||
use crate::net::acme::AcmeProvider;
|
use crate::net::acme::AcmeProvider;
|
||||||
use crate::net::forward::START9_BRIDGE_IFACE;
|
|
||||||
use crate::net::host::binding::{AddSslOptions, BindInfo, BindOptions, NetInfo};
|
|
||||||
use crate::net::host::Host;
|
use crate::net::host::Host;
|
||||||
|
use crate::net::host::binding::{AddSslOptions, BindInfo, BindOptions, NetInfo};
|
||||||
use crate::net::utils::ipv6_is_local;
|
use crate::net::utils::ipv6_is_local;
|
||||||
use crate::net::vhost::AlpnInfo;
|
use crate::net::vhost::AlpnInfo;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -30,7 +31,7 @@ use crate::util::cpupower::Governor;
|
|||||||
use crate::util::lshw::LshwDevice;
|
use crate::util::lshw::LshwDevice;
|
||||||
use crate::util::serde::MaybeUtf8String;
|
use crate::util::serde::MaybeUtf8String;
|
||||||
use crate::version::{Current, VersionT};
|
use crate::version::{Current, VersionT};
|
||||||
use crate::{ARCH, HOST_IP, PLATFORM};
|
use crate::{ARCH, PLATFORM};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -122,11 +123,20 @@ impl Public {
|
|||||||
kiosk,
|
kiosk,
|
||||||
},
|
},
|
||||||
package_data: AllPackageData::default(),
|
package_data: AllPackageData::default(),
|
||||||
ui: serde_json::from_str(include_str!(concat!(
|
ui: {
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
#[cfg(feature = "startd")]
|
||||||
"/../../web/patchdb-ui-seed.json"
|
{
|
||||||
)))
|
serde_json::from_str(include_str!(concat!(
|
||||||
.with_kind(ErrorKind::Deserialization)?,
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/../../web/patchdb-ui-seed.json"
|
||||||
|
)))
|
||||||
|
.with_kind(ErrorKind::Deserialization)?
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "startd"))]
|
||||||
|
{
|
||||||
|
Value::Null
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,8 +212,10 @@ pub struct NetworkInfo {
|
|||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct DnsSettings {
|
pub struct DnsSettings {
|
||||||
pub dhcp_servers: Vec<SocketAddr>,
|
#[ts(type = "string[]")]
|
||||||
pub static_servers: Option<Vec<SocketAddr>>,
|
pub dhcp_servers: VecDeque<SocketAddr>,
|
||||||
|
#[ts(type = "string[] | null")]
|
||||||
|
pub static_servers: Option<VecDeque<SocketAddr>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
@@ -214,64 +226,9 @@ pub struct NetworkInterfaceInfo {
|
|||||||
pub name: Option<InternedString>,
|
pub name: Option<InternedString>,
|
||||||
pub public: Option<bool>,
|
pub public: Option<bool>,
|
||||||
pub secure: Option<bool>,
|
pub secure: Option<bool>,
|
||||||
pub ip_info: Option<IpInfo>,
|
pub ip_info: Option<Arc<IpInfo>>,
|
||||||
}
|
}
|
||||||
impl NetworkInterfaceInfo {
|
impl NetworkInterfaceInfo {
|
||||||
pub fn loopback() -> (&'static GatewayId, &'static Self) {
|
|
||||||
lazy_static! {
|
|
||||||
static ref LO: GatewayId = GatewayId::from(InternedString::intern("lo"));
|
|
||||||
static ref LOOPBACK: NetworkInterfaceInfo = NetworkInterfaceInfo {
|
|
||||||
name: Some(InternedString::from_static("Loopback")),
|
|
||||||
public: Some(false),
|
|
||||||
secure: Some(true),
|
|
||||||
ip_info: Some(IpInfo {
|
|
||||||
name: "lo".into(),
|
|
||||||
scope_id: 1,
|
|
||||||
device_type: None,
|
|
||||||
subnets: [
|
|
||||||
IpNet::new(Ipv4Addr::LOCALHOST.into(), 8).unwrap(),
|
|
||||||
IpNet::new(Ipv6Addr::LOCALHOST.into(), 128).unwrap(),
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
lan_ip: [
|
|
||||||
IpAddr::from(Ipv4Addr::LOCALHOST),
|
|
||||||
IpAddr::from(Ipv6Addr::LOCALHOST)
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
wan_ip: None,
|
|
||||||
ntp_servers: Default::default(),
|
|
||||||
dns_servers: Default::default(),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
(&*LO, &*LOOPBACK)
|
|
||||||
}
|
|
||||||
pub fn lxc_bridge() -> (&'static GatewayId, &'static Self) {
|
|
||||||
lazy_static! {
|
|
||||||
static ref LXCBR0: GatewayId =
|
|
||||||
GatewayId::from(InternedString::intern(START9_BRIDGE_IFACE));
|
|
||||||
static ref LXC_BRIDGE: NetworkInterfaceInfo = NetworkInterfaceInfo {
|
|
||||||
name: Some(InternedString::from_static("LXC Bridge Interface")),
|
|
||||||
public: Some(false),
|
|
||||||
secure: Some(true),
|
|
||||||
ip_info: Some(IpInfo {
|
|
||||||
name: START9_BRIDGE_IFACE.into(),
|
|
||||||
scope_id: 0,
|
|
||||||
device_type: None,
|
|
||||||
subnets: [IpNet::new(HOST_IP.into(), 24).unwrap()]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
lan_ip: [IpAddr::from(HOST_IP)].into_iter().collect(),
|
|
||||||
wan_ip: None,
|
|
||||||
ntp_servers: Default::default(),
|
|
||||||
dns_servers: Default::default(),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
(&*LXCBR0, &*LXC_BRIDGE)
|
|
||||||
}
|
|
||||||
pub fn public(&self) -> bool {
|
pub fn public(&self) -> bool {
|
||||||
self.public.unwrap_or_else(|| {
|
self.public.unwrap_or_else(|| {
|
||||||
!self.ip_info.as_ref().map_or(true, |ip_info| {
|
!self.ip_info.as_ref().map_or(true, |ip_info| {
|
||||||
@@ -303,11 +260,7 @@ impl NetworkInterfaceInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn secure(&self) -> bool {
|
pub fn secure(&self) -> bool {
|
||||||
self.secure.unwrap_or_else(|| {
|
self.secure.unwrap_or(false)
|
||||||
self.ip_info.as_ref().map_or(false, |ip_info| {
|
|
||||||
ip_info.device_type == Some(NetworkInterfaceType::Wireguard)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,13 +284,15 @@ pub struct IpInfo {
|
|||||||
pub dns_servers: OrdSet<IpAddr>,
|
pub dns_servers: OrdSet<IpAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, TS)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum NetworkInterfaceType {
|
pub enum NetworkInterfaceType {
|
||||||
Ethernet,
|
Ethernet,
|
||||||
Wireless,
|
Wireless,
|
||||||
|
Bridge,
|
||||||
Wireguard,
|
Wireguard,
|
||||||
|
Loopback,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||||
@@ -347,6 +302,19 @@ pub enum NetworkInterfaceType {
|
|||||||
pub struct AcmeSettings {
|
pub struct AcmeSettings {
|
||||||
pub contact: Vec<String>,
|
pub contact: Vec<String>,
|
||||||
}
|
}
|
||||||
|
impl DbAccessByKey<AcmeSettings> for Database {
|
||||||
|
type Key<'a> = &'a AcmeProvider;
|
||||||
|
fn access_by_key<'a>(
|
||||||
|
db: &'a Model<Self>,
|
||||||
|
key: Self::Key<'_>,
|
||||||
|
) -> Option<&'a Model<AcmeSettings>> {
|
||||||
|
db.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_acme()
|
||||||
|
.as_idx(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use imbl::OrdMap;
|
use imbl::OrdMap;
|
||||||
@@ -167,6 +168,21 @@ impl<T> Model<Option<T>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Model<Arc<T>> {
|
||||||
|
pub fn deref(self) -> Model<T> {
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
self.transmute(|a| a)
|
||||||
|
}
|
||||||
|
pub fn as_deref(&self) -> &Model<T> {
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
self.transmute_ref(|a| a)
|
||||||
|
}
|
||||||
|
pub fn as_deref_mut(&mut self) -> &mut Model<T> {
|
||||||
|
use patch_db::ModelExt;
|
||||||
|
self.transmute_mut(|a| a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Map: DeserializeOwned + Serialize {
|
pub trait Map: DeserializeOwned + Serialize {
|
||||||
type Key;
|
type Key;
|
||||||
type Value;
|
type Value;
|
||||||
@@ -193,10 +209,10 @@ where
|
|||||||
A: serde::Serialize + serde::de::DeserializeOwned + Ord,
|
A: serde::Serialize + serde::de::DeserializeOwned + Ord,
|
||||||
B: serde::Serialize + serde::de::DeserializeOwned,
|
B: serde::Serialize + serde::de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
type Key = A;
|
type Key = JsonKey<A>;
|
||||||
type Value = B;
|
type Value = B;
|
||||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||||
serde_json::to_string(key).with_kind(ErrorKind::Serialization)
|
serde_json::to_string(&key.0).with_kind(ErrorKind::Serialization)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,13 +232,18 @@ impl<T: Map> Model<T>
|
|||||||
where
|
where
|
||||||
T::Value: Serialize,
|
T::Value: Serialize,
|
||||||
{
|
{
|
||||||
pub fn insert(&mut self, key: &T::Key, value: &T::Value) -> Result<(), Error> {
|
pub fn insert_model(
|
||||||
|
&mut self,
|
||||||
|
key: &T::Key,
|
||||||
|
value: Model<T::Value>,
|
||||||
|
) -> Result<Option<Model<T::Value>>, Error> {
|
||||||
|
use patch_db::ModelExt;
|
||||||
use serde::ser::Error;
|
use serde::ser::Error;
|
||||||
let v = patch_db::value::to_value(value)?;
|
let v = value.into_value();
|
||||||
match &mut self.value {
|
match &mut self.value {
|
||||||
Value::Object(o) => {
|
Value::Object(o) => {
|
||||||
o.insert(T::key_string(key)?, v);
|
let prev = o.insert(T::key_string(key)?, v);
|
||||||
Ok(())
|
Ok(prev.map(|v| Model::from_value(v)))
|
||||||
}
|
}
|
||||||
v => Err(patch_db::value::Error {
|
v => Err(patch_db::value::Error {
|
||||||
source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")),
|
source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")),
|
||||||
@@ -231,6 +252,13 @@ where
|
|||||||
.into()),
|
.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn insert(
|
||||||
|
&mut self,
|
||||||
|
key: &T::Key,
|
||||||
|
value: &T::Value,
|
||||||
|
) -> Result<Option<Model<T::Value>>, Error> {
|
||||||
|
self.insert_model(key, Model::new(value)?)
|
||||||
|
}
|
||||||
pub fn upsert<F>(&mut self, key: &T::Key, value: F) -> Result<&mut Model<T::Value>, Error>
|
pub fn upsert<F>(&mut self, key: &T::Key, value: F) -> Result<&mut Model<T::Value>, Error>
|
||||||
where
|
where
|
||||||
F: FnOnce() -> Result<T::Value, Error>,
|
F: FnOnce() -> Result<T::Value, Error>,
|
||||||
@@ -257,22 +285,6 @@ where
|
|||||||
.into()),
|
.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn insert_model(&mut self, key: &T::Key, value: Model<T::Value>) -> Result<(), Error> {
|
|
||||||
use patch_db::ModelExt;
|
|
||||||
use serde::ser::Error;
|
|
||||||
let v = value.into_value();
|
|
||||||
match &mut self.value {
|
|
||||||
Value::Object(o) => {
|
|
||||||
o.insert(T::key_string(key)?, v);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
v => Err(patch_db::value::Error {
|
|
||||||
source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")),
|
|
||||||
kind: patch_db::value::ErrorKind::Serialization,
|
|
||||||
}
|
|
||||||
.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Map> Model<T>
|
impl<T: Map> Model<T>
|
||||||
@@ -437,6 +449,12 @@ impl<T> std::ops::DerefMut for JsonKey<T> {
|
|||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<T: DeserializeOwned> FromStr for JsonKey<T> {
|
||||||
|
type Err = Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
serde_json::from_str(s).with_kind(ErrorKind::Deserialization)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<T: Serialize> Serialize for JsonKey<T> {
|
impl<T: Serialize> Serialize for JsonKey<T> {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
@@ -449,7 +467,7 @@ impl<T: Serialize> Serialize for JsonKey<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// { "foo": "bar" } -> "{ \"foo\": \"bar\" }"
|
// { "foo": "bar" } -> "{ \"foo\": \"bar\" }"
|
||||||
impl<'de, T: Serialize + DeserializeOwned> Deserialize<'de> for JsonKey<T> {
|
impl<'de, T: DeserializeOwned> Deserialize<'de> for JsonKey<T> {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use models::PackageId;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::PathOrUrl;
|
use crate::util::PathOrUrl;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ pub async fn write_developer_key(
|
|||||||
secret_key: secret.to_bytes(),
|
secret_key: secret.to_bytes(),
|
||||||
public_key: Some(PublicKeyBytes(VerifyingKey::from(secret).to_bytes())),
|
public_key: Some(PublicKeyBytes(VerifyingKey::from(secret).to_bytes())),
|
||||||
};
|
};
|
||||||
let mut file = create_file_mod(path, 0o046).await?;
|
let mut file = create_file_mod(path, 0o640).await?;
|
||||||
file.write_all(
|
file.write_all(
|
||||||
keypair_bytes
|
keypair_bytes
|
||||||
.to_pkcs8_pem(base64ct::LineEnding::default())
|
.to_pkcs8_pem(base64ct::LineEnding::default())
|
||||||
|
|||||||
@@ -80,23 +80,6 @@ impl<Fs: FileSystem> FileSystem for IdMapped<Fs> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn mount<P: AsRef<Path> + Send>(
|
|
||||||
&self,
|
|
||||||
mountpoint: P,
|
|
||||||
mount_type: MountType,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.pre_mount(mountpoint.as_ref()).await?;
|
|
||||||
Command::new("mount.next")
|
|
||||||
.args(
|
|
||||||
default_mount_command(self, mountpoint, mount_type)
|
|
||||||
.await?
|
|
||||||
.get_args(),
|
|
||||||
)
|
|
||||||
.invoke(ErrorKind::Filesystem)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
async fn source_hash(
|
async fn source_hash(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
) -> Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error> {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ impl MountGuard {
|
|||||||
}
|
}
|
||||||
pub async fn unmount(mut self, delete_mountpoint: bool) -> Result<(), Error> {
|
pub async fn unmount(mut self, delete_mountpoint: bool) -> Result<(), Error> {
|
||||||
if self.mounted {
|
if self.mounted {
|
||||||
unmount(&self.mountpoint, false).await?;
|
unmount(&self.mountpoint, !cfg!(feature = "unstable")).await?;
|
||||||
if delete_mountpoint {
|
if delete_mountpoint {
|
||||||
match tokio::fs::remove_dir(&self.mountpoint).await {
|
match tokio::fs::remove_dir(&self.mountpoint).await {
|
||||||
Err(e) if e.raw_os_error() == Some(39) => Ok(()), // directory not empty
|
Err(e) if e.raw_os_error() == Some(39) => Ok(()), // directory not empty
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use const_format::formatcp;
|
|||||||
use futures::{StreamExt, TryStreamExt};
|
use futures::{StreamExt, TryStreamExt};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::ResultExt;
|
use models::ResultExt;
|
||||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
@@ -17,15 +17,16 @@ use ts_rs::TS;
|
|||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::context::{CliContext, InitContext, RpcContext};
|
use crate::context::{CliContext, InitContext, RpcContext};
|
||||||
use crate::db::model::public::ServerStatus;
|
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
|
use crate::db::model::public::ServerStatus;
|
||||||
use crate::developer::OS_DEVELOPER_KEY_PATH;
|
use crate::developer::OS_DEVELOPER_KEY_PATH;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::Hostname;
|
||||||
use crate::middleware::auth::AuthContext;
|
use crate::middleware::auth::AuthContext;
|
||||||
|
use crate::net::gateway::UpgradableListener;
|
||||||
use crate::net::net_controller::{NetController, NetService};
|
use crate::net::net_controller::{NetController, NetService};
|
||||||
use crate::net::socks::DEFAULT_SOCKS_LISTEN;
|
use crate::net::socks::DEFAULT_SOCKS_LISTEN;
|
||||||
use crate::net::utils::find_wifi_iface;
|
use crate::net::utils::find_wifi_iface;
|
||||||
use crate::net::web_server::{UpgradableListener, WebServerAcceptorSetter};
|
use crate::net::web_server::WebServerAcceptorSetter;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{
|
use crate::progress::{
|
||||||
FullProgress, FullProgressTracker, PhaseProgressTrackerHandle, PhasedProgressBar, ProgressUnits,
|
FullProgress, FullProgressTracker, PhaseProgressTrackerHandle, PhasedProgressBar, ProgressUnits,
|
||||||
@@ -34,10 +35,10 @@ use crate::rpc_continuations::{Guid, RpcContinuation};
|
|||||||
use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL};
|
use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL};
|
||||||
use crate::ssh::SSH_DIR;
|
use crate::ssh::SSH_DIR;
|
||||||
use crate::system::{get_mem_info, sync_kiosk};
|
use crate::system::{get_mem_info, sync_kiosk};
|
||||||
use crate::util::io::{open_file, IOHook};
|
use crate::util::io::{IOHook, open_file};
|
||||||
use crate::util::lshw::lshw;
|
use crate::util::lshw::lshw;
|
||||||
use crate::util::net::WebSocketExt;
|
use crate::util::net::WebSocketExt;
|
||||||
use crate::util::{cpupower, Invoke};
|
use crate::util::{Invoke, cpupower};
|
||||||
use crate::{Error, MAIN_DATA, PACKAGE_DATA};
|
use crate::{Error, MAIN_DATA, PACKAGE_DATA};
|
||||||
|
|
||||||
pub const SYSTEM_REBUILD_PATH: &str = "/media/startos/config/system-rebuild";
|
pub const SYSTEM_REBUILD_PATH: &str = "/media/startos/config/system-rebuild";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use clap::builder::ValueParserFactory;
|
|||||||
use clap::{CommandFactory, FromArgMatches, Parser, value_parser};
|
use clap::{CommandFactory, FromArgMatches, Parser, value_parser};
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use exver::VersionRange;
|
use exver::VersionRange;
|
||||||
use futures::{AsyncWriteExt, StreamExt};
|
use futures::StreamExt;
|
||||||
use imbl_value::{InternedString, json};
|
use imbl_value::{InternedString, json};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::{FromStrParser, VersionString};
|
use models::{FromStrParser, VersionString};
|
||||||
@@ -15,7 +15,6 @@ use reqwest::Url;
|
|||||||
use reqwest::header::{CONTENT_LENGTH, HeaderMap};
|
use reqwest::header::{CONTENT_LENGTH, HeaderMap};
|
||||||
use rpc_toolkit::HandlerArgs;
|
use rpc_toolkit::HandlerArgs;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rustyline_async::ReadlineEvent;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
|
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
|
||||||
@@ -34,6 +33,7 @@ use crate::upload::upload;
|
|||||||
use crate::util::Never;
|
use crate::util::Never;
|
||||||
use crate::util::io::open_file;
|
use crate::util::io::open_file;
|
||||||
use crate::util::net::WebSocketExt;
|
use crate::util::net::WebSocketExt;
|
||||||
|
use crate::util::tui::choose;
|
||||||
|
|
||||||
pub const PKG_ARCHIVE_DIR: &str = "package-data/archive";
|
pub const PKG_ARCHIVE_DIR: &str = "package-data/archive";
|
||||||
pub const PKG_PUBLIC_DIR: &str = "package-data/public";
|
pub const PKG_PUBLIC_DIR: &str = "package-data/public";
|
||||||
@@ -175,7 +175,7 @@ pub async fn install(
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SideloadParams {
|
pub struct SideloadParams {
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_session")]
|
#[serde(rename = "__Auth_session")]
|
||||||
session: Option<InternedString>,
|
session: Option<InternedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,47 +483,19 @@ pub async fn cli_install(
|
|||||||
let version = if packages.best.len() == 1 {
|
let version = if packages.best.len() == 1 {
|
||||||
packages.best.pop_first().map(|(k, _)| k).unwrap()
|
packages.best.pop_first().map(|(k, _)| k).unwrap()
|
||||||
} else {
|
} else {
|
||||||
println!(
|
let versions = packages.best.keys().collect::<Vec<_>>();
|
||||||
"Multiple flavors of {id} found. Please select one of the following versions to install:"
|
let version = choose(
|
||||||
);
|
&format!(
|
||||||
let version;
|
concat!(
|
||||||
loop {
|
"Multiple flavors of {id} found. ",
|
||||||
let (mut read, mut output) = rustyline_async::Readline::new("> ".into())
|
"Please select one of the following versions to install:"
|
||||||
.with_kind(ErrorKind::Filesystem)?;
|
),
|
||||||
for (idx, version) in packages.best.keys().enumerate() {
|
id = id
|
||||||
output
|
),
|
||||||
.write_all(format!(" {}) {}\n", idx + 1, version).as_bytes())
|
&versions,
|
||||||
.await?;
|
)
|
||||||
read.add_history_entry(version.to_string());
|
.await?;
|
||||||
}
|
(*version).clone()
|
||||||
if let ReadlineEvent::Line(line) = read.readline().await? {
|
|
||||||
let trimmed = line.trim();
|
|
||||||
match trimmed.parse() {
|
|
||||||
Ok(v) => {
|
|
||||||
if let Some((k, _)) = packages.best.remove_entry(&v) {
|
|
||||||
version = k;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => match trimmed.parse::<usize>() {
|
|
||||||
Ok(i) if (1..=packages.best.len()).contains(&i) => {
|
|
||||||
version = packages.best.keys().nth(i - 1).unwrap().clone();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
eprintln!("invalid selection: {trimmed}");
|
|
||||||
println!("Please select one of the following versions to install:");
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(
|
|
||||||
eyre!("Could not determine precise version to install"),
|
|
||||||
ErrorKind::InvalidRequest,
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
version
|
|
||||||
};
|
};
|
||||||
ctx.call_remote::<RpcContext>(
|
ctx.call_remote::<RpcContext>(
|
||||||
&method.join("."),
|
&method.join("."),
|
||||||
|
|||||||
@@ -80,17 +80,16 @@ use imbl_value::Value;
|
|||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{
|
use rpc_toolkit::{
|
||||||
CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, from_fn, from_fn_async,
|
CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, from_fn, from_fn_async,
|
||||||
from_fn_blocking,
|
from_fn_async_local, from_fn_blocking,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::{
|
use crate::context::{CliContext, DiagnosticContext, InitContext, RpcContext};
|
||||||
CliContext, DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext,
|
|
||||||
};
|
|
||||||
use crate::disk::fsck::RequiresReboot;
|
use crate::disk::fsck::RequiresReboot;
|
||||||
use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
||||||
use crate::system::kiosk;
|
use crate::system::kiosk;
|
||||||
|
use crate::tunnel::context::TunnelUrlParams;
|
||||||
use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable};
|
use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
@@ -139,6 +138,20 @@ pub fn main_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.with_about("Display the API that is currently serving")
|
.with_about("Display the API that is currently serving")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"state",
|
||||||
|
from_fn(|_: InitContext| Ok::<_, Error>(ApiState::Initializing))
|
||||||
|
.with_metadata("authenticated", Value::Bool(false))
|
||||||
|
.with_about("Display the API that is currently serving")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"state",
|
||||||
|
from_fn(|_: DiagnosticContext| Ok::<_, Error>(ApiState::Error))
|
||||||
|
.with_metadata("authenticated", Value::Bool(false))
|
||||||
|
.with_about("Display the API that is currently serving")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"server",
|
"server",
|
||||||
server::<C>()
|
server::<C>()
|
||||||
@@ -191,6 +204,19 @@ pub fn main_api<C: Context>() -> ParentHandler<C> {
|
|||||||
)
|
)
|
||||||
.no_cli(),
|
.no_cli(),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"registry",
|
||||||
|
registry::registry_api::<CliContext>().with_about("Commands related to the registry"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"tunnel",
|
||||||
|
CallRemoteHandler::<RpcContext, _, _, TunnelUrlParams>::new(tunnel::api::tunnel_api())
|
||||||
|
.no_cli(),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"tunnel",
|
||||||
|
tunnel::api::tunnel_api::<CliContext>().with_about("Commands related to StartTunnel"),
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"s9pk",
|
"s9pk",
|
||||||
s9pk::rpc::s9pk().with_about("Commands for interacting with s9pk files"),
|
s9pk::rpc::s9pk().with_about("Commands for interacting with s9pk files"),
|
||||||
@@ -198,6 +224,29 @@ pub fn main_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.subcommand(
|
.subcommand(
|
||||||
"util",
|
"util",
|
||||||
util::rpc::util::<C>().with_about("Command for calculating the blake3 hash of a file"),
|
util::rpc::util::<C>().with_about("Command for calculating the blake3 hash of a file"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"init-key",
|
||||||
|
from_fn_async(developer::init)
|
||||||
|
.no_display()
|
||||||
|
.with_about("Create developer key if it doesn't exist"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"pubkey",
|
||||||
|
from_fn_blocking(developer::pubkey)
|
||||||
|
.with_about("Get public key for developer private key"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"diagnostic",
|
||||||
|
diagnostic::diagnostic::<C>()
|
||||||
|
.with_about("Commands to display logs, restart the server, etc"),
|
||||||
|
)
|
||||||
|
.subcommand("init", init::init_api::<C>())
|
||||||
|
.subcommand("setup", setup::setup::<C>())
|
||||||
|
.subcommand(
|
||||||
|
"install",
|
||||||
|
os_install::install::<C>()
|
||||||
|
.with_about("Commands to list disk info, install StartOS, and reboot"),
|
||||||
);
|
);
|
||||||
if &*PLATFORM != "raspberrypi" {
|
if &*PLATFORM != "raspberrypi" {
|
||||||
api = api.subcommand("kiosk", kiosk::<C>());
|
api = api.subcommand("kiosk", kiosk::<C>());
|
||||||
@@ -343,7 +392,7 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
|||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"install",
|
"install",
|
||||||
from_fn_async(install::cli_install)
|
from_fn_async_local(install::cli_install)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Install a package from a marketplace or via sideloading"),
|
.with_about("Install a package from a marketplace or via sideloading"),
|
||||||
)
|
)
|
||||||
@@ -464,13 +513,6 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
|||||||
backup::package_backup::<C>()
|
backup::package_backup::<C>()
|
||||||
.with_about("Commands for restoring package(s) from backup"),
|
.with_about("Commands for restoring package(s) from backup"),
|
||||||
)
|
)
|
||||||
.subcommand("connect", from_fn_async(service::connect_rpc).no_cli())
|
|
||||||
.subcommand(
|
|
||||||
"connect",
|
|
||||||
from_fn_async(service::connect_rpc_cli)
|
|
||||||
.no_display()
|
|
||||||
.with_about("Connect to a LXC container"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"attach",
|
"attach",
|
||||||
from_fn_async(service::attach)
|
from_fn_async(service::attach)
|
||||||
@@ -484,127 +526,3 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
|||||||
net::host::host_api::<C>().with_about("Manage network hosts for a package"),
|
net::host::host_api::<C>().with_about("Manage network hosts for a package"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diagnostic_api() -> ParentHandler<DiagnosticContext> {
|
|
||||||
ParentHandler::new()
|
|
||||||
.subcommand(
|
|
||||||
"git-info",
|
|
||||||
from_fn(|_: DiagnosticContext| version::git_info())
|
|
||||||
.with_metadata("authenticated", Value::Bool(false))
|
|
||||||
.with_about("Display the githash of StartOS CLI"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"echo",
|
|
||||||
from_fn(echo::<DiagnosticContext>)
|
|
||||||
.with_about("Echo a message")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"state",
|
|
||||||
from_fn(|_: DiagnosticContext| Ok::<_, Error>(ApiState::Error))
|
|
||||||
.with_metadata("authenticated", Value::Bool(false))
|
|
||||||
.with_about("Display the API that is currently serving")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"diagnostic",
|
|
||||||
diagnostic::diagnostic::<DiagnosticContext>()
|
|
||||||
.with_about("Diagnostic commands i.e. logs, restart, rebuild"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_api() -> ParentHandler<InitContext> {
|
|
||||||
ParentHandler::new()
|
|
||||||
.subcommand(
|
|
||||||
"git-info",
|
|
||||||
from_fn(|_: InitContext| version::git_info())
|
|
||||||
.with_metadata("authenticated", Value::Bool(false))
|
|
||||||
.with_about("Display the githash of StartOS CLI"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"echo",
|
|
||||||
from_fn(echo::<InitContext>)
|
|
||||||
.with_about("Echo a message")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"state",
|
|
||||||
from_fn(|_: InitContext| Ok::<_, Error>(ApiState::Initializing))
|
|
||||||
.with_metadata("authenticated", Value::Bool(false))
|
|
||||||
.with_about("Display the API that is currently serving")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"init",
|
|
||||||
init::init_api::<InitContext>()
|
|
||||||
.with_about("Commands to get logs or initialization progress"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup_api() -> ParentHandler<SetupContext> {
|
|
||||||
ParentHandler::new()
|
|
||||||
.subcommand(
|
|
||||||
"git-info",
|
|
||||||
from_fn(|_: SetupContext| version::git_info())
|
|
||||||
.with_metadata("authenticated", Value::Bool(false))
|
|
||||||
.with_about("Display the githash of StartOS CLI"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"echo",
|
|
||||||
from_fn(echo::<SetupContext>)
|
|
||||||
.with_about("Echo a message")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand("setup", setup::setup::<SetupContext>())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn install_api() -> ParentHandler<InstallContext> {
|
|
||||||
ParentHandler::new()
|
|
||||||
.subcommand(
|
|
||||||
"git-info",
|
|
||||||
from_fn(|_: InstallContext| version::git_info())
|
|
||||||
.with_metadata("authenticated", Value::Bool(false))
|
|
||||||
.with_about("Display the githash of StartOS CLI"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"echo",
|
|
||||||
from_fn(echo::<InstallContext>)
|
|
||||||
.with_about("Echo a message")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"install",
|
|
||||||
os_install::install::<InstallContext>()
|
|
||||||
.with_about("Commands to list disk info, install StartOS, and reboot"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expanded_api() -> ParentHandler<CliContext> {
|
|
||||||
main_api()
|
|
||||||
.subcommand(
|
|
||||||
"init",
|
|
||||||
from_fn_async(developer::init)
|
|
||||||
.no_display()
|
|
||||||
.with_about("Create developer key if it doesn't exist"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"pubkey",
|
|
||||||
from_fn_blocking(developer::pubkey)
|
|
||||||
.with_about("Get public key for developer private key"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"diagnostic",
|
|
||||||
diagnostic::diagnostic::<CliContext>()
|
|
||||||
.with_about("Commands to display logs, restart the server, etc"),
|
|
||||||
)
|
|
||||||
.subcommand("setup", setup::setup::<CliContext>())
|
|
||||||
.subcommand(
|
|
||||||
"install",
|
|
||||||
os_install::install::<CliContext>()
|
|
||||||
.with_about("Commands to list disk info, install StartOS, and reboot"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"registry",
|
|
||||||
registry::registry_api::<CliContext>().with_about("Commands related to the registry"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use itertools::Itertools;
|
|||||||
use models::{FromStrParser, PackageId};
|
use models::{FromStrParser, PackageId};
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{
|
use rpc_toolkit::{
|
||||||
from_fn_async, CallRemote, Context, Empty, HandlerArgs, HandlerExt, HandlerFor, ParentHandler,
|
CallRemote, Context, Empty, HandlerArgs, HandlerExt, HandlerFor, ParentHandler, from_fn_async,
|
||||||
};
|
};
|
||||||
use serde::de::{self, DeserializeOwned};
|
use serde::de::{self, DeserializeOwned};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -30,9 +30,9 @@ use crate::error::ResultExt;
|
|||||||
use crate::lxc::ContainerId;
|
use crate::lxc::ContainerId;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations};
|
use crate::rpc_continuations::{Guid, RpcContinuation, RpcContinuations};
|
||||||
|
use crate::util::Invoke;
|
||||||
use crate::util::net::WebSocketExt;
|
use crate::util::net::WebSocketExt;
|
||||||
use crate::util::serde::Reversible;
|
use crate::util::serde::Reversible;
|
||||||
use crate::util::Invoke;
|
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
pub struct LogStream {
|
pub struct LogStream {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use imbl_value::{InOMap, InternedString};
|
|||||||
use models::{FromStrParser, InvalidId, PackageId};
|
use models::{FromStrParser, InvalidId, PackageId};
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{GenericRpcMethod, RpcRequest, RpcResponse};
|
use rpc_toolkit::{GenericRpcMethod, RpcRequest, RpcResponse};
|
||||||
use rustyline_async::{ReadlineEvent, SharedWriter};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -367,6 +366,7 @@ impl LxcContainer {
|
|||||||
}
|
}
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
|
tracing::info!("Connected to socket in {:?}", started.elapsed());
|
||||||
Ok(UnixRpcClient::new(sock_path))
|
Ok(UnixRpcClient::new(sock_path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -470,115 +470,6 @@ pub async fn connect(ctx: &RpcContext, container: &LxcContainer) -> Result<Guid,
|
|||||||
Ok(guid)
|
Ok(guid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn connect_cli(ctx: &CliContext, guid: Guid) -> Result<(), Error> {
|
|
||||||
use futures::SinkExt;
|
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
|
||||||
|
|
||||||
let mut ws = ctx.ws_continuation(guid).await?;
|
|
||||||
let (mut input, mut output) =
|
|
||||||
rustyline_async::Readline::new("> ".into()).with_kind(ErrorKind::Filesystem)?;
|
|
||||||
|
|
||||||
async fn handle_message(
|
|
||||||
msg: Option<Result<Message, tokio_tungstenite::tungstenite::Error>>,
|
|
||||||
output: &mut SharedWriter,
|
|
||||||
) -> Result<bool, Error> {
|
|
||||||
match msg {
|
|
||||||
None => return Ok(true),
|
|
||||||
Some(Ok(Message::Text(txt))) => match serde_json::from_str::<RpcResponse>(&txt) {
|
|
||||||
Ok(RpcResponse { result: Ok(a), .. }) => {
|
|
||||||
output
|
|
||||||
.write_all(
|
|
||||||
(serde_json::to_string(&a).with_kind(ErrorKind::Serialization)? + "\n")
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Ok(RpcResponse { result: Err(e), .. }) => {
|
|
||||||
let e: Error = e.into();
|
|
||||||
tracing::error!("{e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Error Parsing RPC response: {e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(Ok(_)) => (),
|
|
||||||
Some(Err(e)) => {
|
|
||||||
return Err(Error::new(e, ErrorKind::Network));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
line = input.readline() => {
|
|
||||||
let line = line.with_kind(ErrorKind::Filesystem)?;
|
|
||||||
if let ReadlineEvent::Line(line) = line {
|
|
||||||
input.add_history_entry(line.clone());
|
|
||||||
if serde_json::from_str::<RpcRequest>(&line).is_ok() {
|
|
||||||
ws.send(Message::Text(line.into()))
|
|
||||||
.await
|
|
||||||
.with_kind(ErrorKind::Network)?;
|
|
||||||
} else {
|
|
||||||
match shell_words::split(&line) {
|
|
||||||
Ok(command) => {
|
|
||||||
if let Some((method, rest)) = command.split_first() {
|
|
||||||
let mut params = InOMap::new();
|
|
||||||
for arg in rest {
|
|
||||||
if let Some((name, value)) = arg.split_once('=') {
|
|
||||||
params.insert(InternedString::intern(name), if value.is_empty() {
|
|
||||||
Value::Null
|
|
||||||
} else if let Ok(v) = serde_json::from_str(value) {
|
|
||||||
v
|
|
||||||
} else {
|
|
||||||
Value::String(Arc::new(value.into()))
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
tracing::error!("argument without a value: {arg}");
|
|
||||||
tracing::debug!("help: set the value of {arg} with `{arg}=...`");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ws.send(Message::Text(match serde_json::to_string(&RpcRequest {
|
|
||||||
id: None,
|
|
||||||
method: GenericRpcMethod::new(method.into()),
|
|
||||||
params: Value::Object(params),
|
|
||||||
}) {
|
|
||||||
Ok(a) => a.into(),
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Error Serializing Request: {e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
})).await.with_kind(ErrorKind::Network)?;
|
|
||||||
if handle_message(ws.next().await, &mut output).await? {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("{e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ws.send(Message::Close(None)).await.with_kind(ErrorKind::Network)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg = ws.next() => {
|
|
||||||
if handle_message(msg, &mut output).await? {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn stats(ctx: RpcContext) -> Result<BTreeMap<PackageId, Option<ServiceStats>>, Error> {
|
pub async fn stats(ctx: RpcContext) -> Result<BTreeMap<PackageId, Option<ServiceStats>>, Error> {
|
||||||
let ids = ctx.db.peek().await.as_public().as_package_data().keys()?;
|
let ids = ctx.db.peek().await.as_public().as_package_data().keys()?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
|
#[cfg(feature = "backtrace-on-stack-overflow")]
|
||||||
|
unsafe {
|
||||||
|
backtrace_on_stack_overflow::enable()
|
||||||
|
};
|
||||||
startos::bins::startbox()
|
startos::bins::startbox()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,14 +27,11 @@ use tokio::sync::Mutex;
|
|||||||
|
|
||||||
use crate::auth::{Sessions, check_password, write_shadow};
|
use crate::auth::{Sessions, check_password, write_shadow};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::db::model::Database;
|
|
||||||
use crate::middleware::signature::{SignatureAuth, SignatureAuthContext};
|
use crate::middleware::signature::{SignatureAuth, SignatureAuthContext};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::rpc_continuations::OpenAuthedContinuations;
|
use crate::rpc_continuations::OpenAuthedContinuations;
|
||||||
use crate::sign::AnyVerifyingKey;
|
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::util::io::{create_file_mod, read_file_to_string};
|
use crate::util::io::{create_file_mod, read_file_to_string};
|
||||||
use crate::util::iter::TransposeResultIterExt;
|
|
||||||
use crate::util::serde::BASE64;
|
use crate::util::serde::BASE64;
|
||||||
use crate::util::sync::SyncMutex;
|
use crate::util::sync::SyncMutex;
|
||||||
|
|
||||||
@@ -43,7 +40,7 @@ pub trait AuthContext: SignatureAuthContext {
|
|||||||
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str;
|
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str;
|
||||||
fn init_auth_cookie() -> impl Future<Output = Result<(), Error>> + Send {
|
fn init_auth_cookie() -> impl Future<Output = Result<(), Error>> + Send {
|
||||||
async {
|
async {
|
||||||
let mut file = create_file_mod(Self::LOCAL_AUTH_COOKIE_PATH, 0o046).await?;
|
let mut file = create_file_mod(Self::LOCAL_AUTH_COOKIE_PATH, 0o640).await?;
|
||||||
file.write_all(BASE64.encode(random::<[u8; 32]>()).as_bytes())
|
file.write_all(BASE64.encode(random::<[u8; 32]>()).as_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
file.sync_all().await?;
|
file.sync_all().await?;
|
||||||
@@ -66,65 +63,6 @@ pub trait AuthContext: SignatureAuthContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignatureAuthContext for RpcContext {
|
|
||||||
type Database = Database;
|
|
||||||
type AdditionalMetadata = ();
|
|
||||||
type CheckPubkeyRes = ();
|
|
||||||
fn db(&self) -> &TypedPatchDb<Self::Database> {
|
|
||||||
&self.db
|
|
||||||
}
|
|
||||||
async fn sig_context(
|
|
||||||
&self,
|
|
||||||
) -> impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send {
|
|
||||||
let peek = self.db.peek().await;
|
|
||||||
self.account
|
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.hostnames()
|
|
||||||
.into_iter()
|
|
||||||
.map(Ok)
|
|
||||||
.chain(
|
|
||||||
peek.as_public()
|
|
||||||
.as_server_info()
|
|
||||||
.as_network()
|
|
||||||
.as_host()
|
|
||||||
.as_public_domains()
|
|
||||||
.keys()
|
|
||||||
.map(|k| k.into_iter())
|
|
||||||
.transpose(),
|
|
||||||
)
|
|
||||||
.chain(
|
|
||||||
peek.as_public()
|
|
||||||
.as_server_info()
|
|
||||||
.as_network()
|
|
||||||
.as_host()
|
|
||||||
.as_private_domains()
|
|
||||||
.de()
|
|
||||||
.map(|k| k.into_iter())
|
|
||||||
.transpose(),
|
|
||||||
)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
fn check_pubkey(
|
|
||||||
db: &Model<Self::Database>,
|
|
||||||
pubkey: Option<&AnyVerifyingKey>,
|
|
||||||
_: Self::AdditionalMetadata,
|
|
||||||
) -> Result<Self::CheckPubkeyRes, Error> {
|
|
||||||
if let Some(pubkey) = pubkey {
|
|
||||||
if db.as_private().as_auth_pubkeys().de()?.contains(pubkey) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::new(
|
|
||||||
eyre!("Developer Key is not authorized"),
|
|
||||||
ErrorKind::IncorrectPassword,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
async fn post_auth_hook(&self, _: Self::CheckPubkeyRes, _: &RpcRequest) -> Result<(), Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl AuthContext for RpcContext {
|
impl AuthContext for RpcContext {
|
||||||
const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie";
|
const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie";
|
||||||
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:startos";
|
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:startos";
|
||||||
@@ -439,7 +377,7 @@ impl<C: AuthContext> Middleware<C> for Auth {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Some(user_agent) = self.user_agent.as_ref().and_then(|h| h.to_str().ok()) {
|
if let Some(user_agent) = self.user_agent.as_ref().and_then(|h| h.to_str().ok()) {
|
||||||
request.params["__auth_userAgent"] =
|
request.params["__Auth_userAgent"] =
|
||||||
Value::String(Arc::new(user_agent.to_owned()))
|
Value::String(Arc::new(user_agent.to_owned()))
|
||||||
// TODO: will this panic?
|
// TODO: will this panic?
|
||||||
}
|
}
|
||||||
@@ -458,7 +396,7 @@ impl<C: AuthContext> Middleware<C> for Auth {
|
|||||||
{
|
{
|
||||||
match HasValidSession::from_header(self.cookie.as_ref(), context).await? {
|
match HasValidSession::from_header(self.cookie.as_ref(), context).await? {
|
||||||
HasValidSession(SessionType::Session(s)) if metadata.get_session => {
|
HasValidSession(SessionType::Session(s)) if metadata.get_session => {
|
||||||
request.params["__auth_session"] =
|
request.params["__Auth_session"] =
|
||||||
Value::String(Arc::new(s.hashed().deref().to_owned()));
|
Value::String(Arc::new(s.hashed().deref().to_owned()));
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|||||||
55
core/startos/src/middleware/connect_info.rs
Normal file
55
core/startos/src/middleware/connect_info.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use axum::extract::Request;
|
||||||
|
use axum::response::Response;
|
||||||
|
use imbl_value::json;
|
||||||
|
use rpc_toolkit::Middleware;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct ConnectInfo {
|
||||||
|
peer_addr: Option<SocketAddr>,
|
||||||
|
local_addr: Option<SocketAddr>,
|
||||||
|
}
|
||||||
|
impl ConnectInfo {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Metadata {
|
||||||
|
get_connect_info: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Context: Send + Sync + 'static> Middleware<Context> for ConnectInfo {
|
||||||
|
type Metadata = Metadata;
|
||||||
|
async fn process_http_request(
|
||||||
|
&mut self,
|
||||||
|
_: &Context,
|
||||||
|
request: &mut Request,
|
||||||
|
) -> Result<(), Response> {
|
||||||
|
if let Some(axum::extract::ConnectInfo((peer, local))) = request.extensions().get().cloned()
|
||||||
|
{
|
||||||
|
self.peer_addr = Some(peer);
|
||||||
|
self.local_addr = Some(local);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
async fn process_rpc_request(
|
||||||
|
&mut self,
|
||||||
|
_: &Context,
|
||||||
|
metadata: Self::Metadata,
|
||||||
|
request: &mut rpc_toolkit::RpcRequest,
|
||||||
|
) -> Result<(), rpc_toolkit::RpcResponse> {
|
||||||
|
if metadata.get_connect_info {
|
||||||
|
if let Some(peer_addr) = self.peer_addr {
|
||||||
|
request.params["__ConnectInfo_peer_addr"] = json!(peer_addr);
|
||||||
|
}
|
||||||
|
if let Some(local_addr) = self.local_addr {
|
||||||
|
request.params["__ConnectInfo_local_addr"] = json!(local_addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod connect_info;
|
||||||
pub mod cors;
|
pub mod cors;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod signature;
|
pub mod signature;
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
|||||||
|
|
||||||
use axum::body::Body;
|
use axum::body::Body;
|
||||||
use axum::extract::Request;
|
use axum::extract::Request;
|
||||||
use http::HeaderValue;
|
use http::{HeaderMap, HeaderValue};
|
||||||
|
use reqwest::Client;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{Context, Middleware, RpcRequest, RpcResponse};
|
use rpc_toolkit::{Context, Middleware, RpcRequest, RpcResponse};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -13,13 +14,17 @@ use serde::de::DeserializeOwned;
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::{CliContext, RpcContext};
|
||||||
|
use crate::db::model::Database;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::sign::commitment::Commitment;
|
use crate::sign::commitment::Commitment;
|
||||||
use crate::sign::commitment::request::RequestCommitment;
|
use crate::sign::commitment::request::RequestCommitment;
|
||||||
use crate::sign::{AnySignature, AnySigningKey, AnyVerifyingKey, SignatureScheme};
|
use crate::sign::{AnySignature, AnySigningKey, AnyVerifyingKey, SignatureScheme};
|
||||||
|
use crate::util::iter::TransposeResultIterExt;
|
||||||
use crate::util::serde::Base64;
|
use crate::util::serde::Base64;
|
||||||
|
|
||||||
|
pub const AUTH_SIG_HEADER: &str = "X-StartOS-Auth-Sig";
|
||||||
|
|
||||||
pub trait SignatureAuthContext: Context {
|
pub trait SignatureAuthContext: Context {
|
||||||
type Database: HasModel<Model = Model<Self::Database>> + Send + Sync;
|
type Database: HasModel<Model = Model<Self::Database>> + Send + Sync;
|
||||||
type AdditionalMetadata: DeserializeOwned + Send;
|
type AdditionalMetadata: DeserializeOwned + Send;
|
||||||
@@ -41,7 +46,82 @@ pub trait SignatureAuthContext: Context {
|
|||||||
) -> impl Future<Output = Result<(), Error>> + Send;
|
) -> impl Future<Output = Result<(), Error>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const AUTH_SIG_HEADER: &str = "X-StartOS-Auth-Sig";
|
impl SignatureAuthContext for RpcContext {
|
||||||
|
type Database = Database;
|
||||||
|
type AdditionalMetadata = ();
|
||||||
|
type CheckPubkeyRes = ();
|
||||||
|
fn db(&self) -> &TypedPatchDb<Self::Database> {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
async fn sig_context(
|
||||||
|
&self,
|
||||||
|
) -> impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send {
|
||||||
|
let peek = self.db.peek().await;
|
||||||
|
self.account.peek(|a| {
|
||||||
|
a.hostnames()
|
||||||
|
.into_iter()
|
||||||
|
.map(Ok)
|
||||||
|
.chain(
|
||||||
|
peek.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_host()
|
||||||
|
.as_public_domains()
|
||||||
|
.keys()
|
||||||
|
.map(|k| k.into_iter())
|
||||||
|
.transpose(),
|
||||||
|
)
|
||||||
|
.chain(
|
||||||
|
peek.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_host()
|
||||||
|
.as_private_domains()
|
||||||
|
.de()
|
||||||
|
.map(|k| k.into_iter())
|
||||||
|
.transpose(),
|
||||||
|
)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn check_pubkey(
|
||||||
|
db: &Model<Self::Database>,
|
||||||
|
pubkey: Option<&AnyVerifyingKey>,
|
||||||
|
_: Self::AdditionalMetadata,
|
||||||
|
) -> Result<Self::CheckPubkeyRes, Error> {
|
||||||
|
if let Some(pubkey) = pubkey {
|
||||||
|
if db.as_private().as_auth_pubkeys().de()?.contains(pubkey) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("Developer Key is not authorized"),
|
||||||
|
ErrorKind::IncorrectPassword,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
async fn post_auth_hook(&self, _: Self::CheckPubkeyRes, _: &RpcRequest) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SigningContext {
|
||||||
|
fn signing_key(&self) -> Result<AnySigningKey, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SigningContext for CliContext {
|
||||||
|
fn signing_key(&self) -> Result<AnySigningKey, Error> {
|
||||||
|
Ok(AnySigningKey::Ed25519(self.developer_key()?.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SigningContext for RpcContext {
|
||||||
|
fn signing_key(&self) -> Result<AnySigningKey, Error> {
|
||||||
|
Ok(AnySigningKey::Ed25519(
|
||||||
|
self.account.peek(|a| a.developer_key.clone()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct Metadata<Additional> {
|
pub struct Metadata<Additional> {
|
||||||
@@ -203,7 +283,7 @@ impl<C: SignatureAuthContext> Middleware<C> for SignatureAuth {
|
|||||||
let signer = self.signer.take().transpose()?;
|
let signer = self.signer.take().transpose()?;
|
||||||
if metadata.get_signer {
|
if metadata.get_signer {
|
||||||
if let Some(signer) = &signer {
|
if let Some(signer) = &signer {
|
||||||
request.params["__auth_signer"] = to_value(signer)?;
|
request.params["__Auth_signer"] = to_value(signer)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let db = context.db().peek().await;
|
let db = context.db().peek().await;
|
||||||
@@ -216,10 +296,11 @@ impl<C: SignatureAuthContext> Middleware<C> for SignatureAuth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn call_remote(
|
pub async fn call_remote<Ctx: SigningContext + AsRef<Client>>(
|
||||||
ctx: &CliContext,
|
ctx: &Ctx,
|
||||||
url: Url,
|
url: Url,
|
||||||
sig_context: &str,
|
headers: HeaderMap,
|
||||||
|
sig_context: Option<&str>,
|
||||||
method: &str,
|
method: &str,
|
||||||
params: Value,
|
params: Value,
|
||||||
) -> Result<Value, RpcError> {
|
) -> Result<Value, RpcError> {
|
||||||
@@ -235,16 +316,16 @@ pub async fn call_remote(
|
|||||||
};
|
};
|
||||||
let body = serde_json::to_vec(&rpc_req)?;
|
let body = serde_json::to_vec(&rpc_req)?;
|
||||||
let mut req = ctx
|
let mut req = ctx
|
||||||
.client
|
.as_ref()
|
||||||
.request(Method::POST, url)
|
.request(Method::POST, url)
|
||||||
.header(CONTENT_TYPE, "application/json")
|
.header(CONTENT_TYPE, "application/json")
|
||||||
.header(ACCEPT, "application/json")
|
.header(ACCEPT, "application/json")
|
||||||
.header(CONTENT_LENGTH, body.len());
|
.header(CONTENT_LENGTH, body.len())
|
||||||
if let Ok(key) = ctx.developer_key() {
|
.headers(headers);
|
||||||
|
if let (Some(sig_ctx), Ok(key)) = (sig_context, ctx.signing_key()) {
|
||||||
req = req.header(
|
req = req.header(
|
||||||
AUTH_SIG_HEADER,
|
AUTH_SIG_HEADER,
|
||||||
SignatureHeader::sign(&AnySigningKey::Ed25519(key.clone()), &body, sig_context)?
|
SignatureHeader::sign(&key, &body, sig_ctx)?.to_header(),
|
||||||
.to_header(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let res = req.body(body).send().await?;
|
let res = req.body(body).send().await?;
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
use std::net::IpAddr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_acme::acme::Identifier;
|
use async_acme::acme::{ACME_TLS_ALPN_NAME, Identifier};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use clap::builder::ValueParserFactory;
|
use clap::builder::ValueParserFactory;
|
||||||
|
use futures::StreamExt;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::{ErrorData, FromStrParser};
|
use models::{ErrorData, FromStrParser};
|
||||||
@@ -11,14 +14,217 @@ use openssl::pkey::{PKey, Private};
|
|||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio_rustls::rustls::ServerConfig;
|
||||||
|
use tokio_rustls::rustls::crypto::CryptoProvider;
|
||||||
|
use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||||
|
use tokio_rustls::rustls::server::ClientHello;
|
||||||
|
use tokio_rustls::rustls::sign::CertifiedKey;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
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::net::ssl::should_use_cert;
|
||||||
|
use crate::net::tls::{SingleCertResolver, TlsHandler};
|
||||||
|
use crate::net::web_server::Accept;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::{Pem, Pkcs8Doc};
|
use crate::util::serde::{Pem, Pkcs8Doc};
|
||||||
|
use crate::util::sync::{SyncMutex, Watch};
|
||||||
|
|
||||||
|
pub type AcmeTlsAlpnCache =
|
||||||
|
Arc<SyncMutex<BTreeMap<InternedString, Watch<Option<Arc<CertifiedKey>>>>>>;
|
||||||
|
|
||||||
|
pub struct AcmeTlsHandler<M: HasModel, S> {
|
||||||
|
pub db: TypedPatchDb<M>,
|
||||||
|
pub acme_cache: AcmeTlsAlpnCache,
|
||||||
|
pub crypto_provider: Arc<CryptoProvider>,
|
||||||
|
pub get_provider: S,
|
||||||
|
pub in_progress: Watch<BTreeSet<BTreeSet<InternedString>>>,
|
||||||
|
}
|
||||||
|
impl<M, S> AcmeTlsHandler<M, S>
|
||||||
|
where
|
||||||
|
for<'a> M: DbAccessByKey<AcmeSettings, Key<'a> = &'a AcmeProvider>
|
||||||
|
+ DbAccessMut<AcmeCertStore>
|
||||||
|
+ HasModel<Model = Model<M>>
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
|
S: GetAcmeProvider + Clone,
|
||||||
|
{
|
||||||
|
pub async fn get_cert(&self, san_info: &BTreeSet<InternedString>) -> Option<CertifiedKey> {
|
||||||
|
let provider = self.get_provider.get_provider(san_info).await?;
|
||||||
|
let provider = provider.as_ref();
|
||||||
|
loop {
|
||||||
|
let peek = self.db.peek().await;
|
||||||
|
let store = <M as DbAccess<AcmeCertStore>>::access(&peek);
|
||||||
|
if let Some(cert) = store
|
||||||
|
.as_certs()
|
||||||
|
.as_idx(&provider.0)
|
||||||
|
.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()?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.in_progress.send_if_modified(|x| {
|
||||||
|
if !x.contains(san_info) {
|
||||||
|
x.insert(san_info.clone());
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
self.in_progress
|
||||||
|
.clone()
|
||||||
|
.wait_for(|x| !x.contains(san_info))
|
||||||
|
.await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contact = <M as DbAccessByKey<AcmeSettings>>::access_by_key(&peek, &provider)?
|
||||||
|
.as_contact()
|
||||||
|
.de()
|
||||||
|
.log_err()?;
|
||||||
|
|
||||||
|
let identifiers: Vec<_> = san_info
|
||||||
|
.iter()
|
||||||
|
.map(|d| match d.parse::<IpAddr>() {
|
||||||
|
Ok(a) => Identifier::Ip(a),
|
||||||
|
_ => Identifier::Dns((&**d).into()),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let cache_entries = san_info
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(|d| (d, Watch::new(None)))
|
||||||
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
self.acme_cache.mutate(|c| {
|
||||||
|
c.extend(cache_entries.iter().map(|(k, v)| (k.clone(), v.clone())));
|
||||||
|
});
|
||||||
|
|
||||||
|
let cert = async_acme::rustls_helper::order(
|
||||||
|
|identifier, cert| {
|
||||||
|
let domain = InternedString::from_display(&identifier);
|
||||||
|
if let Some(entry) = cache_entries.get(&domain) {
|
||||||
|
entry.send(Some(Arc::new(cert)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
provider.0.as_str(),
|
||||||
|
&identifiers,
|
||||||
|
Some(&AcmeCertCache(&self.db)),
|
||||||
|
&contact,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.log_err()?;
|
||||||
|
|
||||||
|
self.acme_cache
|
||||||
|
.mutate(|c| c.retain(|c, _| !cache_entries.contains_key(c)));
|
||||||
|
|
||||||
|
self.in_progress.send_modify(|i| i.remove(san_info));
|
||||||
|
|
||||||
|
return Some(cert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait GetAcmeProvider {
|
||||||
|
fn get_provider<'a, 'b: 'a>(
|
||||||
|
&'b self,
|
||||||
|
san_info: &'a BTreeSet<InternedString>,
|
||||||
|
) -> impl Future<Output = Option<impl AsRef<AcmeProvider> + Send + 'b>> + Send + 'a;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A, M, S> TlsHandler<'a, A> for Arc<AcmeTlsHandler<M, S>>
|
||||||
|
where
|
||||||
|
A: Accept + 'a,
|
||||||
|
<A as Accept>::Metadata: Send + Sync,
|
||||||
|
for<'m> M: DbAccessByKey<AcmeSettings, Key<'m> = &'m AcmeProvider>
|
||||||
|
+ DbAccessMut<AcmeCertStore>
|
||||||
|
+ HasModel<Model = Model<M>>
|
||||||
|
+ Send
|
||||||
|
+ Sync,
|
||||||
|
S: GetAcmeProvider + Clone + Send + Sync,
|
||||||
|
{
|
||||||
|
async fn get_config(
|
||||||
|
&'a mut self,
|
||||||
|
hello: &'a ClientHello<'a>,
|
||||||
|
_: &'a <A as Accept>::Metadata,
|
||||||
|
) -> Option<ServerConfig> {
|
||||||
|
let domain = hello.server_name()?;
|
||||||
|
if hello
|
||||||
|
.alpn()
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.any(|a| a == ACME_TLS_ALPN_NAME)
|
||||||
|
{
|
||||||
|
let cert = self
|
||||||
|
.acme_cache
|
||||||
|
.peek(|c| c.get(domain).cloned())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
eyre!("No challenge recv available for {domain}"),
|
||||||
|
ErrorKind::OpenSsl,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.log_err()?;
|
||||||
|
tracing::info!("Waiting for verification cert for {domain}");
|
||||||
|
let cert = cert
|
||||||
|
.filter(|c| futures::future::ready(c.is_some()))
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.flatten()?;
|
||||||
|
tracing::info!("Verification cert received for {domain}");
|
||||||
|
let mut cfg = ServerConfig::builder_with_provider(self.crypto_provider.clone())
|
||||||
|
.with_safe_default_protocol_versions()
|
||||||
|
.log_err()?
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_cert_resolver(Arc::new(SingleCertResolver(cert)));
|
||||||
|
|
||||||
|
cfg.alpn_protocols = vec![ACME_TLS_ALPN_NAME.to_vec()];
|
||||||
|
tracing::info!("performing ACME auth challenge");
|
||||||
|
|
||||||
|
return Some(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let domains: BTreeSet<InternedString> = [domain.into()].into_iter().collect();
|
||||||
|
|
||||||
|
let crypto_provider = self.crypto_provider.clone();
|
||||||
|
if let Some(cert) = self.get_cert(&domains).await {
|
||||||
|
return Some(
|
||||||
|
ServerConfig::builder_with_provider(crypto_provider)
|
||||||
|
.with_safe_default_protocol_versions()
|
||||||
|
.log_err()?
|
||||||
|
.with_no_client_auth()
|
||||||
|
.with_cert_resolver(Arc::new(SingleCertResolver(Arc::new(cert)))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
|
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
@@ -32,29 +238,35 @@ impl AcmeCertStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DbAccess<AcmeCertStore> for Database {
|
||||||
|
fn access<'a>(db: &'a Model<Self>) -> &'a Model<AcmeCertStore> {
|
||||||
|
db.as_private().as_key_store().as_acme()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DbAccessMut<AcmeCertStore> for Database {
|
||||||
|
fn access_mut<'a>(db: &'a mut Model<Self>) -> &'a mut Model<AcmeCertStore> {
|
||||||
|
db.as_private_mut().as_key_store_mut().as_acme_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct AcmeCert {
|
pub struct AcmeCert {
|
||||||
pub key: Pem<PKey<Private>>,
|
pub key: Pem<PKey<Private>>,
|
||||||
pub fullchain: Vec<Pem<X509>>,
|
pub fullchain: Vec<Pem<X509>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AcmeCertCache<'a>(pub &'a TypedPatchDb<Database>);
|
pub struct AcmeCertCache<'a, M: HasModel>(pub &'a TypedPatchDb<M>);
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> {
|
impl<'a, M> async_acme::cache::AcmeCache for AcmeCertCache<'a, M>
|
||||||
|
where
|
||||||
|
M: HasModel<Model = Model<M>> + DbAccessMut<AcmeCertStore> + Send + Sync,
|
||||||
|
{
|
||||||
type Error = ErrorData;
|
type Error = ErrorData;
|
||||||
|
|
||||||
async fn read_account(&self, contacts: &[&str]) -> Result<Option<Vec<u8>>, Self::Error> {
|
async fn read_account(&self, contacts: &[&str]) -> Result<Option<Vec<u8>>, Self::Error> {
|
||||||
let contacts = JsonKey::new(contacts.into_iter().map(|s| (*s).to_owned()).collect_vec());
|
let contacts = JsonKey::new(contacts.into_iter().map(|s| (*s).to_owned()).collect_vec());
|
||||||
let Some(account) = self
|
let peek = self.0.peek().await;
|
||||||
.0
|
let Some(account) = M::access(&peek).as_accounts().as_idx(&contacts) else {
|
||||||
.peek()
|
|
||||||
.await
|
|
||||||
.into_private()
|
|
||||||
.into_key_store()
|
|
||||||
.into_acme()
|
|
||||||
.into_accounts()
|
|
||||||
.into_idx(&contacts)
|
|
||||||
else {
|
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
Ok(Some(account.de()?.0.document.into_vec()))
|
Ok(Some(account.de()?.0.document.into_vec()))
|
||||||
@@ -68,9 +280,7 @@ impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> {
|
|||||||
};
|
};
|
||||||
self.0
|
self.0
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
db.as_private_mut()
|
M::access_mut(db)
|
||||||
.as_key_store_mut()
|
|
||||||
.as_acme_mut()
|
|
||||||
.as_accounts_mut()
|
.as_accounts_mut()
|
||||||
.insert(&contacts, &Pem::new(key))
|
.insert(&contacts, &Pem::new(key))
|
||||||
})
|
})
|
||||||
@@ -96,20 +306,25 @@ impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> {
|
|||||||
let directory_url = directory_url
|
let directory_url = directory_url
|
||||||
.parse::<Url>()
|
.parse::<Url>()
|
||||||
.with_kind(ErrorKind::ParseUrl)?;
|
.with_kind(ErrorKind::ParseUrl)?;
|
||||||
let Some(cert) = self
|
let peek = self.0.peek().await;
|
||||||
.0
|
let Some(cert) = M::access(&peek)
|
||||||
.peek()
|
.as_certs()
|
||||||
.await
|
.as_idx(&directory_url)
|
||||||
.into_private()
|
.and_then(|a| a.as_idx(&identifiers))
|
||||||
.into_key_store()
|
|
||||||
.into_acme()
|
|
||||||
.into_certs()
|
|
||||||
.into_idx(&directory_url)
|
|
||||||
.and_then(|a| a.into_idx(&identifiers))
|
|
||||||
else {
|
else {
|
||||||
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
|
||||||
@@ -160,9 +375,7 @@ impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> {
|
|||||||
};
|
};
|
||||||
self.0
|
self.0
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
db.as_private_mut()
|
M::access_mut(db)
|
||||||
.as_key_store_mut()
|
|
||||||
.as_acme_mut()
|
|
||||||
.as_certs_mut()
|
.as_certs_mut()
|
||||||
.upsert(&directory_url, || Ok(BTreeMap::new()))?
|
.upsert(&directory_url, || Ok(BTreeMap::new()))?
|
||||||
.insert(&identifiers, &cert)
|
.insert(&identifiers, &cert)
|
||||||
@@ -235,6 +448,11 @@ impl AsRef<str> for AcmeProvider {
|
|||||||
self.0.as_str()
|
self.0.as_str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl AsRef<AcmeProvider> for AcmeProvider {
|
||||||
|
fn as_ref(&self) -> &AcmeProvider {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
impl ValueParserFactory for AcmeProvider {
|
impl ValueParserFactory for AcmeProvider {
|
||||||
type Parser = FromStrParser<Self>;
|
type Parser = FromStrParser<Self>;
|
||||||
fn value_parser() -> Self::Parser {
|
fn value_parser() -> Self::Parser {
|
||||||
|
|||||||
@@ -1,43 +1,46 @@
|
|||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, VecDeque};
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
use hickory_client::client::Client;
|
use hickory_client::client::Client;
|
||||||
|
use hickory_client::proto::DnsHandle;
|
||||||
use hickory_client::proto::runtime::TokioRuntimeProvider;
|
use hickory_client::proto::runtime::TokioRuntimeProvider;
|
||||||
use hickory_client::proto::tcp::TcpClientStream;
|
use hickory_client::proto::tcp::TcpClientStream;
|
||||||
use hickory_client::proto::udp::UdpClientStream;
|
use hickory_client::proto::udp::UdpClientStream;
|
||||||
use hickory_client::proto::xfer::{DnsExchangeBackground, DnsRequestOptions};
|
use hickory_client::proto::xfer::DnsRequestOptions;
|
||||||
use hickory_client::proto::DnsHandle;
|
use hickory_server::ServerFuture;
|
||||||
use hickory_server::authority::MessageResponseBuilder;
|
use hickory_server::authority::MessageResponseBuilder;
|
||||||
use hickory_server::proto::op::{Header, ResponseCode};
|
use hickory_server::proto::op::{Header, ResponseCode};
|
||||||
use hickory_server::proto::rr::{Name, Record, RecordType};
|
use hickory_server::proto::rr::{Name, Record, RecordType};
|
||||||
use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
|
use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
|
||||||
use hickory_server::ServerFuture;
|
|
||||||
use imbl::OrdMap;
|
use imbl::OrdMap;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::{GatewayId, OptionExt, PackageId};
|
use models::{GatewayId, OptionExt, PackageId};
|
||||||
|
use patch_db::json_ptr::JsonPointer;
|
||||||
use rpc_toolkit::{
|
use rpc_toolkit::{
|
||||||
from_fn_async, from_fn_blocking, Context, HandlerArgs, HandlerExt, ParentHandler,
|
Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async, from_fn_blocking,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::net::{TcpListener, UdpSocket};
|
use tokio::net::{TcpListener, UdpSocket};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::public::NetworkInterfaceInfo;
|
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
|
use crate::db::model::public::NetworkInterfaceInfo;
|
||||||
use crate::net::gateway::NetworkInterfaceWatcher;
|
use crate::net::gateway::NetworkInterfaceWatcher;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
use crate::util::io::file_string_stream;
|
use crate::util::io::file_string_stream;
|
||||||
use crate::util::serde::{display_serializable, HandlerExtSerde};
|
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||||
use crate::util::sync::{SyncRwLock, Watch};
|
use crate::util::sync::{SyncRwLock, Watch};
|
||||||
|
|
||||||
pub fn dns_api<C: Context>() -> ParentHandler<C> {
|
pub fn dns_api<C: Context>() -> ParentHandler<C> {
|
||||||
@@ -63,7 +66,36 @@ pub fn dns_api<C: Context>() -> ParentHandler<C> {
|
|||||||
"set-static",
|
"set-static",
|
||||||
from_fn_async(set_static_dns)
|
from_fn_async(set_static_dns)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Set static DNS servers"),
|
.with_about("Set static DNS servers")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"dump-table",
|
||||||
|
from_fn_async(dump_table)
|
||||||
|
.with_display_serializable()
|
||||||
|
.with_custom_display_fn(|HandlerArgs { params, .. }, res| {
|
||||||
|
use prettytable::*;
|
||||||
|
|
||||||
|
if let Some(format) = params.format {
|
||||||
|
return display_serializable(format, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut table = Table::new();
|
||||||
|
table.add_row(row![bc => "FQDN", "DESTINATION"]);
|
||||||
|
for (hostname, destination) in res {
|
||||||
|
if let Some(ip) = destination {
|
||||||
|
table.add_row(row![hostname, ip]);
|
||||||
|
} else {
|
||||||
|
table.add_row(row![hostname, "SELF"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.print_tty(false)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.with_about("Dump address resolution table")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +171,38 @@ pub async fn set_static_dns(
|
|||||||
.result
|
.result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn dump_table(
|
||||||
|
ctx: RpcContext,
|
||||||
|
) -> Result<BTreeMap<InternedString, Option<IpAddr>>, Error> {
|
||||||
|
Ok(ctx
|
||||||
|
.net_controller
|
||||||
|
.dns
|
||||||
|
.resolve
|
||||||
|
.upgrade()
|
||||||
|
.or_not_found("DnsController")?
|
||||||
|
.peek(|map| {
|
||||||
|
map.private_domains
|
||||||
|
.iter()
|
||||||
|
.map(|(d, _)| (d.clone(), None))
|
||||||
|
.chain(map.services.iter().filter_map(|(svc, ip)| {
|
||||||
|
ip.iter()
|
||||||
|
.find(|(_, rc)| rc.strong_count() > 0)
|
||||||
|
.map(|(ip, _)| {
|
||||||
|
(
|
||||||
|
svc.as_ref().map_or(
|
||||||
|
InternedString::from_static("startos"),
|
||||||
|
|svc| {
|
||||||
|
InternedString::from_display(&lazy_format!("{svc}.startos"))
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Some(IpAddr::V4(*ip)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.collect()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct ResolveMap {
|
struct ResolveMap {
|
||||||
private_domains: BTreeMap<InternedString, Weak<()>>,
|
private_domains: BTreeMap<InternedString, Weak<()>>,
|
||||||
@@ -161,97 +225,146 @@ impl DnsClient {
|
|||||||
Self {
|
Self {
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
_thread: tokio::spawn(async move {
|
_thread: tokio::spawn(async move {
|
||||||
loop {
|
let (bg, mut runner) = BackgroundJobQueue::new();
|
||||||
if let Err::<(), Error>(e) = async {
|
runner
|
||||||
let mut stream = file_string_stream("/run/systemd/resolve/resolv.conf")
|
.run_while(async move {
|
||||||
.filter_map(|a| futures::future::ready(a.transpose()))
|
let dhcp_ns_db = db.clone();
|
||||||
.boxed();
|
bg.add_job(async move {
|
||||||
let mut conf: String = stream
|
loop {
|
||||||
.next()
|
if let Err(e) = async {
|
||||||
.await
|
let mut stream =
|
||||||
.or_not_found("/run/systemd/resolve/resolv.conf")??;
|
file_string_stream("/run/systemd/resolve/resolv.conf")
|
||||||
let mut prev_nameservers = Vec::new();
|
.filter_map(|a| futures::future::ready(a.transpose()))
|
||||||
let mut bg = BTreeMap::<SocketAddr, BoxFuture<_>>::new();
|
.boxed();
|
||||||
loop {
|
while let Some(conf) = stream.next().await {
|
||||||
let nameservers = conf
|
let conf: String = conf?;
|
||||||
.lines()
|
let mut nameservers = conf
|
||||||
.map(|l| l.trim())
|
.lines()
|
||||||
.filter_map(|l| l.strip_prefix("nameserver "))
|
.map(|l| l.trim())
|
||||||
.skip(2)
|
.filter_map(|l| l.strip_prefix("nameserver "))
|
||||||
.map(|n| {
|
.map(|n| {
|
||||||
n.parse::<SocketAddr>()
|
n.parse::<SocketAddr>().or_else(|_| {
|
||||||
.or_else(|_| n.parse::<IpAddr>().map(|a| (a, 53).into()))
|
n.parse::<IpAddr>().map(|a| (a, 53).into())
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
})
|
||||||
let static_nameservers = db
|
.collect::<Result<VecDeque<_>, _>>()?;
|
||||||
.mutate(|db| {
|
if nameservers
|
||||||
let dns = db
|
.front()
|
||||||
.as_public_mut()
|
.map_or(false, |addr| addr.ip().is_loopback())
|
||||||
.as_server_info_mut()
|
|
||||||
.as_network_mut()
|
|
||||||
.as_dns_mut();
|
|
||||||
dns.as_dhcp_servers_mut().ser(&nameservers)?;
|
|
||||||
dns.as_static_servers().de()
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.result?;
|
|
||||||
let nameservers = static_nameservers.unwrap_or(nameservers);
|
|
||||||
if nameservers != prev_nameservers {
|
|
||||||
let mut existing: BTreeMap<_, _> =
|
|
||||||
client.peek(|c| c.iter().cloned().collect());
|
|
||||||
let mut new = Vec::with_capacity(nameservers.len());
|
|
||||||
for addr in &nameservers {
|
|
||||||
if let Some(existing) = existing.remove(addr) {
|
|
||||||
new.push((*addr, existing));
|
|
||||||
} else {
|
|
||||||
let client = if let Ok((client, bg_thread)) =
|
|
||||||
Client::connect(
|
|
||||||
UdpClientStream::builder(
|
|
||||||
*addr,
|
|
||||||
TokioRuntimeProvider::new(),
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
bg.insert(*addr, bg_thread.boxed());
|
nameservers.pop_front();
|
||||||
client
|
}
|
||||||
} else {
|
if nameservers.front().map_or(false, |addr| {
|
||||||
let (stream, sender) = TcpClientStream::new(
|
addr.ip() == IpAddr::from([1, 1, 1, 1])
|
||||||
*addr,
|
}) {
|
||||||
None,
|
nameservers.pop_front();
|
||||||
Some(Duration::from_secs(30)),
|
}
|
||||||
TokioRuntimeProvider::new(),
|
dhcp_ns_db
|
||||||
);
|
.mutate(|db| {
|
||||||
let (client, bg_thread) =
|
let dns = db
|
||||||
Client::new(stream, sender, None)
|
.as_public_mut()
|
||||||
.await
|
.as_server_info_mut()
|
||||||
.with_kind(ErrorKind::Network)?;
|
.as_network_mut()
|
||||||
bg.insert(*addr, bg_thread.boxed());
|
.as_dns_mut();
|
||||||
client
|
dns.as_dhcp_servers_mut().ser(&nameservers)
|
||||||
};
|
})
|
||||||
new.push((*addr, client));
|
.await
|
||||||
|
.result?
|
||||||
}
|
}
|
||||||
|
Ok::<_, Error>(())
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("{e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
bg.retain(|n, _| nameservers.iter().any(|a| a == n));
|
|
||||||
prev_nameservers = nameservers;
|
|
||||||
client.replace(new);
|
|
||||||
}
|
}
|
||||||
tokio::select! {
|
});
|
||||||
c = stream.next() => conf = c.or_not_found("/run/systemd/resolve/resolv.conf")??,
|
loop {
|
||||||
_ = futures::future::join(
|
if let Err::<(), Error>(e) = async {
|
||||||
futures::future::join_all(bg.values_mut()),
|
let mut dns_changed = db
|
||||||
futures::future::pending::<()>(),
|
.subscribe(
|
||||||
) => (),
|
"/public/serverInfo/network/dns"
|
||||||
|
.parse::<JsonPointer>()
|
||||||
|
.with_kind(ErrorKind::Database)?,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let mut prev_nameservers = VecDeque::new();
|
||||||
|
let mut bg = BTreeMap::<SocketAddr, BoxFuture<_>>::new();
|
||||||
|
loop {
|
||||||
|
let dns = db
|
||||||
|
.peek()
|
||||||
|
.await
|
||||||
|
.into_public()
|
||||||
|
.into_server_info()
|
||||||
|
.into_network()
|
||||||
|
.into_dns();
|
||||||
|
let nameservers = dns
|
||||||
|
.as_static_servers()
|
||||||
|
.transpose_ref()
|
||||||
|
.unwrap_or_else(|| dns.as_dhcp_servers())
|
||||||
|
.de()?;
|
||||||
|
if nameservers != prev_nameservers {
|
||||||
|
let mut existing: BTreeMap<_, _> =
|
||||||
|
client.peek(|c| c.iter().cloned().collect());
|
||||||
|
let mut new = Vec::with_capacity(nameservers.len());
|
||||||
|
for addr in &nameservers {
|
||||||
|
if let Some(existing) = existing.remove(addr) {
|
||||||
|
new.push((*addr, existing));
|
||||||
|
} else {
|
||||||
|
let client = if let Ok((client, bg_thread)) =
|
||||||
|
Client::connect(
|
||||||
|
UdpClientStream::builder(
|
||||||
|
*addr,
|
||||||
|
TokioRuntimeProvider::new(),
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
bg.insert(*addr, bg_thread.boxed());
|
||||||
|
client
|
||||||
|
} else {
|
||||||
|
let (stream, sender) = TcpClientStream::new(
|
||||||
|
*addr,
|
||||||
|
None,
|
||||||
|
Some(Duration::from_secs(30)),
|
||||||
|
TokioRuntimeProvider::new(),
|
||||||
|
);
|
||||||
|
let (client, bg_thread) =
|
||||||
|
Client::new(stream, sender, None)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
bg.insert(*addr, bg_thread.fuse().boxed());
|
||||||
|
client
|
||||||
|
};
|
||||||
|
new.push((*addr, client));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bg.retain(|n, _| nameservers.iter().any(|a| a == n));
|
||||||
|
prev_nameservers = nameservers;
|
||||||
|
client.replace(new);
|
||||||
|
}
|
||||||
|
futures::future::select(
|
||||||
|
dns_changed.recv().boxed(),
|
||||||
|
futures::future::join(
|
||||||
|
futures::future::join_all(bg.values_mut()),
|
||||||
|
futures::future::pending::<()>(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("{e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
.await
|
.await;
|
||||||
{
|
|
||||||
tracing::error!("{e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.into(),
|
.into(),
|
||||||
}
|
}
|
||||||
@@ -269,22 +382,40 @@ impl DnsClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref LOCALHOST: Name = Name::from_ascii("localhost").unwrap();
|
||||||
|
static ref STARTOS: Name = Name::from_ascii("startos").unwrap();
|
||||||
|
static ref EMBASSY: Name = Name::from_ascii("embassy").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
struct Resolver {
|
struct Resolver {
|
||||||
client: DnsClient,
|
client: DnsClient,
|
||||||
net_iface: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
net_iface: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
||||||
resolve: Arc<SyncRwLock<ResolveMap>>,
|
resolve: Arc<SyncRwLock<ResolveMap>>,
|
||||||
}
|
}
|
||||||
impl Resolver {
|
impl Resolver {
|
||||||
fn resolve(&self, name: &Name, src: IpAddr) -> Option<Vec<IpAddr>> {
|
fn resolve(&self, name: &Name, mut src: IpAddr) -> Option<Vec<IpAddr>> {
|
||||||
|
if name.zone_of(&*LOCALHOST) {
|
||||||
|
return Some(vec![Ipv4Addr::LOCALHOST.into(), Ipv6Addr::LOCALHOST.into()]);
|
||||||
|
}
|
||||||
|
src = match src {
|
||||||
|
IpAddr::V6(v6) => {
|
||||||
|
if let Some(v4) = v6.to_ipv4_mapped() {
|
||||||
|
IpAddr::V4(v4)
|
||||||
|
} else {
|
||||||
|
IpAddr::V6(v6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a => a,
|
||||||
|
};
|
||||||
self.resolve.peek(|r| {
|
self.resolve.peek(|r| {
|
||||||
if r.private_domains
|
if r.private_domains
|
||||||
.get(&*name.to_lowercase().to_ascii())
|
.get(&*name.to_lowercase().to_utf8().trim_end_matches('.'))
|
||||||
.map_or(false, |d| d.strong_count() > 0)
|
.map_or(false, |d| d.strong_count() > 0)
|
||||||
{
|
{
|
||||||
if let Some(res) = self.net_iface.peek(|i| {
|
if let Some(res) = self.net_iface.peek(|i| {
|
||||||
i.values()
|
i.values()
|
||||||
.chain([NetworkInterfaceInfo::lxc_bridge().1])
|
.filter_map(|i| i.ip_info.as_ref())
|
||||||
.flat_map(|i| i.ip_info.as_ref())
|
|
||||||
.find(|i| i.subnets.iter().any(|s| s.contains(&src)))
|
.find(|i| i.subnets.iter().any(|s| s.contains(&src)))
|
||||||
.map(|ip_info| {
|
.map(|ip_info| {
|
||||||
let mut res = ip_info.subnets.iter().collect::<Vec<_>>();
|
let mut res = ip_info.subnets.iter().collect::<Vec<_>>();
|
||||||
@@ -293,38 +424,34 @@ impl Resolver {
|
|||||||
})
|
})
|
||||||
}) {
|
}) {
|
||||||
return Some(res);
|
return Some(res);
|
||||||
|
} else {
|
||||||
|
tracing::warn!("Could not determine source interface of {src}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match name.iter().next_back() {
|
if STARTOS.zone_of(name) || EMBASSY.zone_of(name) {
|
||||||
Some(b"embassy") | Some(b"startos") => {
|
let Ok(pkg) = name
|
||||||
if let Some(pkg) = name.iter().rev().skip(1).next() {
|
.trim_to(2)
|
||||||
if let Some(ip) = r.services.get(&Some(
|
.iter()
|
||||||
std::str::from_utf8(pkg)
|
.next()
|
||||||
.unwrap_or_default()
|
.map(std::str::from_utf8)
|
||||||
.parse()
|
.transpose()
|
||||||
.unwrap_or_default(),
|
.map_err(|_| ())
|
||||||
)) {
|
.and_then(|s| s.map(PackageId::from_str).transpose().map_err(|_| ()))
|
||||||
Some(
|
else {
|
||||||
ip.iter()
|
return None;
|
||||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
};
|
||||||
.map(|(ip, _)| (*ip).into())
|
if let Some(ip) = r.services.get(&pkg) {
|
||||||
.collect(),
|
Some(
|
||||||
)
|
ip.iter()
|
||||||
} else {
|
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||||
None
|
.map(|(ip, _)| (*ip).into())
|
||||||
}
|
.collect(),
|
||||||
} else if let Some(ip) = r.services.get(&None) {
|
)
|
||||||
Some(
|
} else {
|
||||||
ip.iter()
|
None
|
||||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
|
||||||
.map(|(ip, _)| (*ip).into())
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => None,
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -351,11 +478,7 @@ impl RequestHandler for Resolver {
|
|||||||
header,
|
header,
|
||||||
&ip.into_iter()
|
&ip.into_iter()
|
||||||
.filter_map(|a| {
|
.filter_map(|a| {
|
||||||
if let IpAddr::V4(a) = a {
|
if let IpAddr::V4(a) = a { Some(a) } else { None }
|
||||||
Some(a)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.map(|ip| {
|
.map(|ip| {
|
||||||
Record::from_rdata(
|
Record::from_rdata(
|
||||||
@@ -381,11 +504,7 @@ impl RequestHandler for Resolver {
|
|||||||
header,
|
header,
|
||||||
&ip.into_iter()
|
&ip.into_iter()
|
||||||
.filter_map(|a| {
|
.filter_map(|a| {
|
||||||
if let IpAddr::V6(a) = a {
|
if let IpAddr::V6(a) = a { Some(a) } else { None }
|
||||||
Some(a)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.map(|ip| {
|
.map(|ip| {
|
||||||
Record::from_rdata(
|
Record::from_rdata(
|
||||||
@@ -420,16 +539,22 @@ impl RequestHandler for Resolver {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let query = query.original().clone();
|
let query = query.original().clone();
|
||||||
let mut streams = self.client.lookup(query, DnsRequestOptions::default());
|
let mut opt = DnsRequestOptions::default();
|
||||||
|
opt.recursion_desired = request.recursion_desired();
|
||||||
|
let mut streams = self.client.lookup(query, opt);
|
||||||
let mut err = None;
|
let mut err = None;
|
||||||
for stream in streams.iter_mut() {
|
for stream in streams.iter_mut() {
|
||||||
match tokio::time::timeout(Duration::from_secs(5), stream.next()).await {
|
match tokio::time::timeout(Duration::from_secs(5), stream.next()).await {
|
||||||
Ok(Some(Err(e))) => err = Some(e),
|
Ok(Some(Err(e))) => err = Some(e),
|
||||||
Ok(Some(Ok(msg))) => {
|
Ok(Some(Ok(msg))) => {
|
||||||
|
let mut header = msg.header().clone();
|
||||||
|
header.set_id(request.id());
|
||||||
|
header.set_checking_disabled(request.checking_disabled());
|
||||||
|
header.set_recursion_available(true);
|
||||||
return response_handle
|
return response_handle
|
||||||
.send_response(
|
.send_response(
|
||||||
MessageResponseBuilder::from_message_request(&*request).build(
|
MessageResponseBuilder::from_message_request(&*request).build(
|
||||||
Header::response_from_request(request.header()),
|
header,
|
||||||
msg.answers(),
|
msg.answers(),
|
||||||
msg.name_servers(),
|
msg.name_servers(),
|
||||||
&msg.soa().map(|s| s.to_owned().into_record_of_rdata()),
|
&msg.soa().map(|s| s.to_owned().into_record_of_rdata()),
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::net::{IpAddr, SocketAddr, SocketAddrV6};
|
use std::net::{IpAddr, SocketAddrV4};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
use id_pool::IdPool;
|
use id_pool::IdPool;
|
||||||
|
use iddqd::{IdOrdItem, IdOrdMap};
|
||||||
use imbl::OrdMap;
|
use imbl::OrdMap;
|
||||||
use models::GatewayId;
|
use models::GatewayId;
|
||||||
|
use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::public::NetworkInterfaceInfo;
|
use crate::db::model::public::NetworkInterfaceInfo;
|
||||||
use crate::net::gateway::{DynInterfaceFilter, InterfaceFilter};
|
use crate::net::gateway::{DynInterfaceFilter, InterfaceFilter};
|
||||||
use crate::net::utils::ipv6_is_link_local;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
|
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||||
use crate::util::sync::Watch;
|
use crate::util::sync::Watch;
|
||||||
|
|
||||||
pub const START9_BRIDGE_IFACE: &str = "lxcbr0";
|
pub const START9_BRIDGE_IFACE: &str = "lxcbr0";
|
||||||
@@ -42,163 +46,399 @@ impl AvailablePorts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ForwardRequest {
|
pub fn forward_api<C: Context>() -> ParentHandler<C> {
|
||||||
external: u16,
|
ParentHandler::new().subcommand(
|
||||||
target: SocketAddr,
|
"dump-table",
|
||||||
filter: DynInterfaceFilter,
|
from_fn_async(
|
||||||
|
|ctx: RpcContext| async move { ctx.net_controller.forward.dump_table().await },
|
||||||
|
)
|
||||||
|
.with_display_serializable()
|
||||||
|
.with_custom_display_fn(|HandlerArgs { params, .. }, res| {
|
||||||
|
use prettytable::*;
|
||||||
|
|
||||||
|
if let Some(format) = params.format {
|
||||||
|
return display_serializable(format, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut table = Table::new();
|
||||||
|
table.add_row(row![bc => "FROM", "TO", "FILTER"]);
|
||||||
|
|
||||||
|
for (external, target) in res.0 {
|
||||||
|
table.add_row(row![external, target.target, target.filter]);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.print_tty(false)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ForwardMapping {
|
||||||
|
source: SocketAddrV4,
|
||||||
|
target: SocketAddrV4,
|
||||||
rc: Weak<()>,
|
rc: Weak<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ForwardEntry {
|
#[derive(Default)]
|
||||||
external: u16,
|
struct PortForwardState {
|
||||||
target: SocketAddr,
|
mappings: BTreeMap<SocketAddrV4, ForwardMapping>, // source -> target
|
||||||
prev_filter: DynInterfaceFilter,
|
|
||||||
forwards: BTreeMap<SocketAddr, GatewayId>,
|
|
||||||
rc: Weak<()>,
|
|
||||||
}
|
}
|
||||||
impl ForwardEntry {
|
|
||||||
fn new(external: u16, target: SocketAddr, rc: Weak<()>) -> Self {
|
|
||||||
Self {
|
|
||||||
external,
|
|
||||||
target,
|
|
||||||
prev_filter: false.into_dyn(),
|
|
||||||
forwards: BTreeMap::new(),
|
|
||||||
rc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take(&mut self) -> Self {
|
impl PortForwardState {
|
||||||
Self {
|
async fn add_forward(
|
||||||
external: self.external,
|
|
||||||
target: self.target,
|
|
||||||
prev_filter: std::mem::replace(&mut self.prev_filter, false.into_dyn()),
|
|
||||||
forwards: std::mem::take(&mut self.forwards),
|
|
||||||
rc: self.rc.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn destroy(mut self) -> Result<(), Error> {
|
|
||||||
while let Some((source, interface)) = self.forwards.pop_first() {
|
|
||||||
unforward(interface.as_str(), source, self.target).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
source: SocketAddrV4,
|
||||||
filter: Option<DynInterfaceFilter>,
|
target: SocketAddrV4,
|
||||||
) -> Result<(), Error> {
|
) -> Result<Arc<()>, Error> {
|
||||||
if self.rc.strong_count() == 0 {
|
if let Some(existing) = self.mappings.get_mut(&source) {
|
||||||
return self.take().destroy().await;
|
if existing.target == target {
|
||||||
}
|
if let Some(existing_rc) = existing.rc.upgrade() {
|
||||||
let filter_ref = filter.as_ref().unwrap_or(&self.prev_filter);
|
return Ok(existing_rc);
|
||||||
let mut keep = BTreeSet::<SocketAddr>::new();
|
} else {
|
||||||
for (iface, info) in ip_info
|
let rc = Arc::new(());
|
||||||
.iter()
|
existing.rc = Arc::downgrade(&rc);
|
||||||
.chain([NetworkInterfaceInfo::loopback()])
|
return Ok(rc);
|
||||||
.filter(|(id, info)| filter_ref.filter(*id, *info))
|
}
|
||||||
{
|
} else {
|
||||||
if let Some(ip_info) = &info.ip_info {
|
// Different target, need to remove old and add new
|
||||||
for ipnet in &ip_info.subnets {
|
if let Some(mapping) = self.mappings.remove(&source) {
|
||||||
let addr = match ipnet.addr() {
|
unforward(mapping.source, mapping.target).await?;
|
||||||
IpAddr::V6(ip6) => SocketAddrV6::new(
|
|
||||||
ip6,
|
|
||||||
self.external,
|
|
||||||
0,
|
|
||||||
if ipv6_is_link_local(ip6) {
|
|
||||||
ip_info.scope_id
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
ip => SocketAddr::new(ip, self.external),
|
|
||||||
};
|
|
||||||
keep.insert(addr);
|
|
||||||
if !self.forwards.contains_key(&addr) {
|
|
||||||
forward(iface.as_str(), addr, self.target).await?;
|
|
||||||
self.forwards.insert(addr, iface.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let rm = self
|
|
||||||
.forwards
|
let rc = Arc::new(());
|
||||||
.keys()
|
forward(source, target).await?;
|
||||||
.copied()
|
self.mappings.insert(
|
||||||
.filter(|a| !keep.contains(a))
|
source,
|
||||||
.collect::<Vec<_>>();
|
ForwardMapping {
|
||||||
for rm in rm {
|
source,
|
||||||
if let Some((source, interface)) = self.forwards.remove_entry(&rm) {
|
target,
|
||||||
unforward(interface.as_str(), source, self.target).await?;
|
rc: Arc::downgrade(&rc),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(rc)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn gc(&mut self) -> Result<(), Error> {
|
||||||
|
let to_remove: Vec<SocketAddrV4> = self
|
||||||
|
.mappings
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, mapping)| mapping.rc.strong_count() == 0)
|
||||||
|
.map(|(source, _)| *source)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for source in to_remove {
|
||||||
|
if let Some(mapping) = self.mappings.remove(&source) {
|
||||||
|
unforward(mapping.source, mapping.target).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(filter) = filter {
|
|
||||||
self.prev_filter = filter;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_request(
|
fn dump(&self) -> BTreeMap<SocketAddrV4, SocketAddrV4> {
|
||||||
&mut self,
|
self.mappings
|
||||||
ForwardRequest {
|
.iter()
|
||||||
external,
|
.filter(|(_, mapping)| mapping.rc.strong_count() > 0)
|
||||||
target,
|
.map(|(source, mapping)| (*source, mapping.target))
|
||||||
filter,
|
.collect()
|
||||||
rc,
|
|
||||||
}: ForwardRequest,
|
|
||||||
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if external != self.external || target != self.target {
|
|
||||||
self.take().destroy().await?;
|
|
||||||
*self = Self::new(external, target, rc);
|
|
||||||
self.update(ip_info, Some(filter)).await?;
|
|
||||||
} else {
|
|
||||||
if self.prev_filter != filter {
|
|
||||||
self.update(ip_info, Some(filter)).await?;
|
|
||||||
}
|
|
||||||
self.rc = rc;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Drop for ForwardEntry {
|
|
||||||
|
impl Drop for PortForwardState {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if !self.forwards.is_empty() {
|
if !self.mappings.is_empty() {
|
||||||
let take = self.take();
|
let mappings = std::mem::take(&mut self.mappings);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
take.destroy().await.log_err();
|
for (_, mapping) in mappings {
|
||||||
|
unforward(mapping.source, mapping.target).await.log_err();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
enum PortForwardCommand {
|
||||||
struct ForwardState {
|
AddForward {
|
||||||
state: BTreeMap<u16, ForwardEntry>,
|
source: SocketAddrV4,
|
||||||
|
target: SocketAddrV4,
|
||||||
|
respond: oneshot::Sender<Result<Arc<()>, Error>>,
|
||||||
|
},
|
||||||
|
Gc {
|
||||||
|
respond: oneshot::Sender<Result<(), Error>>,
|
||||||
|
},
|
||||||
|
Dump {
|
||||||
|
respond: oneshot::Sender<BTreeMap<SocketAddrV4, SocketAddrV4>>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
impl ForwardState {
|
|
||||||
|
pub struct PortForwardController {
|
||||||
|
req: mpsc::UnboundedSender<PortForwardCommand>,
|
||||||
|
_thread: NonDetachingJoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PortForwardController {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (req_send, mut req_recv) = mpsc::unbounded_channel::<PortForwardCommand>();
|
||||||
|
let thread = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
||||||
|
while let Err(e) = async {
|
||||||
|
Command::new("sysctl")
|
||||||
|
.arg("-w")
|
||||||
|
.arg("net.ipv4.ip_forward=1")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await?;
|
||||||
|
if Command::new("iptables")
|
||||||
|
.arg("-t")
|
||||||
|
.arg("nat")
|
||||||
|
.arg("-C")
|
||||||
|
.arg("POSTROUTING")
|
||||||
|
.arg("-j")
|
||||||
|
.arg("MASQUERADE")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
Command::new("iptables")
|
||||||
|
.arg("-t")
|
||||||
|
.arg("nat")
|
||||||
|
.arg("-A")
|
||||||
|
.arg("POSTROUTING")
|
||||||
|
.arg("-j")
|
||||||
|
.arg("MASQUERADE")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok::<_, Error>(())
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("error initializing PortForwardController: {e:#}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
|
}
|
||||||
|
let mut state = PortForwardState::default();
|
||||||
|
while let Some(cmd) = req_recv.recv().await {
|
||||||
|
match cmd {
|
||||||
|
PortForwardCommand::AddForward {
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
respond,
|
||||||
|
} => {
|
||||||
|
let result = state.add_forward(source, target).await;
|
||||||
|
respond.send(result).ok();
|
||||||
|
}
|
||||||
|
PortForwardCommand::Gc { respond } => {
|
||||||
|
let result = state.gc().await;
|
||||||
|
respond.send(result).ok();
|
||||||
|
}
|
||||||
|
PortForwardCommand::Dump { respond } => {
|
||||||
|
respond.send(state.dump()).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
req: req_send,
|
||||||
|
_thread: thread,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_forward(
|
||||||
|
&self,
|
||||||
|
source: SocketAddrV4,
|
||||||
|
target: SocketAddrV4,
|
||||||
|
) -> Result<Arc<()>, Error> {
|
||||||
|
let (send, recv) = oneshot::channel();
|
||||||
|
self.req
|
||||||
|
.send(PortForwardCommand::AddForward {
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
respond: send,
|
||||||
|
})
|
||||||
|
.map_err(err_has_exited)?;
|
||||||
|
|
||||||
|
recv.await.map_err(err_has_exited)?
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn gc(&self) -> Result<(), Error> {
|
||||||
|
let (send, recv) = oneshot::channel();
|
||||||
|
self.req
|
||||||
|
.send(PortForwardCommand::Gc { respond: send })
|
||||||
|
.map_err(err_has_exited)?;
|
||||||
|
|
||||||
|
recv.await.map_err(err_has_exited)?
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn dump(&self) -> Result<BTreeMap<SocketAddrV4, SocketAddrV4>, Error> {
|
||||||
|
let (send, recv) = oneshot::channel();
|
||||||
|
self.req
|
||||||
|
.send(PortForwardCommand::Dump { respond: send })
|
||||||
|
.map_err(err_has_exited)?;
|
||||||
|
|
||||||
|
recv.await.map_err(err_has_exited)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InterfaceForwardRequest {
|
||||||
|
external: u16,
|
||||||
|
target: SocketAddrV4,
|
||||||
|
filter: DynInterfaceFilter,
|
||||||
|
rc: Arc<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct InterfaceForwardEntry {
|
||||||
|
external: u16,
|
||||||
|
filter: BTreeMap<DynInterfaceFilter, (SocketAddrV4, Weak<()>)>,
|
||||||
|
// Maps source SocketAddr -> strong reference for the forward created in PortForwardController
|
||||||
|
forwards: BTreeMap<SocketAddrV4, Arc<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdOrdItem for InterfaceForwardEntry {
|
||||||
|
type Key<'a> = u16;
|
||||||
|
fn key(&self) -> Self::Key<'_> {
|
||||||
|
self.external
|
||||||
|
}
|
||||||
|
|
||||||
|
iddqd::id_upcast!();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceForwardEntry {
|
||||||
|
fn new(external: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
external,
|
||||||
|
filter: BTreeMap::new(),
|
||||||
|
forwards: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
&mut self,
|
||||||
|
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
|
port_forward: &PortForwardController,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut keep = BTreeSet::<SocketAddrV4>::new();
|
||||||
|
|
||||||
|
for (iface, info) in ip_info.iter() {
|
||||||
|
if let Some(target) = self
|
||||||
|
.filter
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, (_, rc))| rc.strong_count() > 0)
|
||||||
|
.find(|(filter, _)| filter.filter(iface, info))
|
||||||
|
.map(|(_, (target, _))| *target)
|
||||||
|
{
|
||||||
|
if let Some(ip_info) = &info.ip_info {
|
||||||
|
for addr in ip_info.subnets.iter().filter_map(|net| {
|
||||||
|
if let IpAddr::V4(ip) = net.addr() {
|
||||||
|
Some(SocketAddrV4::new(ip, self.external))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
keep.insert(addr);
|
||||||
|
if !self.forwards.contains_key(&addr) {
|
||||||
|
let rc = port_forward.add_forward(addr, target).await?;
|
||||||
|
self.forwards.insert(addr, rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove forwards that should no longer exist (drops the strong references)
|
||||||
|
self.forwards.retain(|addr, _| keep.contains(addr));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_request(
|
||||||
|
&mut self,
|
||||||
|
InterfaceForwardRequest {
|
||||||
|
external,
|
||||||
|
target,
|
||||||
|
filter,
|
||||||
|
mut rc,
|
||||||
|
}: InterfaceForwardRequest,
|
||||||
|
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
|
port_forward: &PortForwardController,
|
||||||
|
) -> Result<Arc<()>, Error> {
|
||||||
|
if external != self.external {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("Mismatched external port in InterfaceForwardEntry"),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = self
|
||||||
|
.filter
|
||||||
|
.entry(filter)
|
||||||
|
.or_insert_with(|| (target, Arc::downgrade(&rc)));
|
||||||
|
if entry.0 != target {
|
||||||
|
entry.0 = target;
|
||||||
|
entry.1 = Arc::downgrade(&rc);
|
||||||
|
}
|
||||||
|
if let Some(existing) = entry.1.upgrade() {
|
||||||
|
rc = existing;
|
||||||
|
} else {
|
||||||
|
entry.1 = Arc::downgrade(&rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update(ip_info, port_forward).await?;
|
||||||
|
|
||||||
|
Ok(rc)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn gc(
|
||||||
|
&mut self,
|
||||||
|
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
|
port_forward: &PortForwardController,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.filter.retain(|_, (_, rc)| rc.strong_count() > 0);
|
||||||
|
|
||||||
|
self.update(ip_info, port_forward).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InterfaceForwardState {
|
||||||
|
port_forward: PortForwardController,
|
||||||
|
state: IdOrdMap<InterfaceForwardEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceForwardState {
|
||||||
|
fn new(port_forward: PortForwardController) -> Self {
|
||||||
|
Self {
|
||||||
|
port_forward,
|
||||||
|
state: IdOrdMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceForwardState {
|
||||||
async fn handle_request(
|
async fn handle_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
request: ForwardRequest,
|
request: InterfaceForwardRequest,
|
||||||
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<Arc<()>, Error> {
|
||||||
self.state
|
self.state
|
||||||
.entry(request.external)
|
.entry(request.external)
|
||||||
.or_insert_with(|| ForwardEntry::new(request.external, request.target, Weak::new()))
|
.or_insert_with(|| InterfaceForwardEntry::new(request.external))
|
||||||
.update_request(request, ip_info)
|
.update_request(request, ip_info, &self.port_forward)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync(
|
async fn sync(
|
||||||
&mut self,
|
&mut self,
|
||||||
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
for entry in self.state.values_mut() {
|
for mut entry in self.state.iter_mut() {
|
||||||
entry.update(ip_info, None).await?;
|
entry.gc(ip_info, &self.port_forward).await?;
|
||||||
}
|
}
|
||||||
self.state.retain(|_, fwd| !fwd.forwards.is_empty());
|
|
||||||
Ok(())
|
self.port_forward.gc().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,28 +449,88 @@ fn err_has_exited<T>(_: T) -> Error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PortForwardController {
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
req: mpsc::UnboundedSender<(Option<ForwardRequest>, oneshot::Sender<Result<(), Error>>)>,
|
pub struct ForwardTable(pub BTreeMap<u16, ForwardTarget>);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct ForwardTarget {
|
||||||
|
pub target: SocketAddrV4,
|
||||||
|
pub filter: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&InterfaceForwardState> for ForwardTable {
|
||||||
|
fn from(value: &InterfaceForwardState) -> Self {
|
||||||
|
Self(
|
||||||
|
value
|
||||||
|
.state
|
||||||
|
.iter()
|
||||||
|
.flat_map(|entry| {
|
||||||
|
entry
|
||||||
|
.filter
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, (_, rc))| rc.strong_count() > 0)
|
||||||
|
.map(|(filter, (target, _))| {
|
||||||
|
(
|
||||||
|
entry.external,
|
||||||
|
ForwardTarget {
|
||||||
|
target: *target,
|
||||||
|
filter: format!("{:?}", filter),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InterfaceForwardCommand {
|
||||||
|
Forward(
|
||||||
|
InterfaceForwardRequest,
|
||||||
|
oneshot::Sender<Result<Arc<()>, Error>>,
|
||||||
|
),
|
||||||
|
Sync(oneshot::Sender<Result<(), Error>>),
|
||||||
|
DumpTable(oneshot::Sender<ForwardTable>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
use crate::net::gateway::SecureFilter;
|
||||||
|
|
||||||
|
assert_ne!(
|
||||||
|
false.into_dyn(),
|
||||||
|
SecureFilter { secure: false }.into_dyn().into_dyn()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InterfacePortForwardController {
|
||||||
|
req: mpsc::UnboundedSender<InterfaceForwardCommand>,
|
||||||
_thread: NonDetachingJoinHandle<()>,
|
_thread: NonDetachingJoinHandle<()>,
|
||||||
}
|
}
|
||||||
impl PortForwardController {
|
|
||||||
|
impl InterfacePortForwardController {
|
||||||
pub fn new(mut ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>) -> Self {
|
pub fn new(mut ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>) -> Self {
|
||||||
let (req_send, mut req_recv) = mpsc::unbounded_channel::<(
|
let port_forward = PortForwardController::new();
|
||||||
Option<ForwardRequest>,
|
|
||||||
oneshot::Sender<Result<(), Error>>,
|
let (req_send, mut req_recv) = mpsc::unbounded_channel::<InterfaceForwardCommand>();
|
||||||
)>();
|
|
||||||
let thread = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
let thread = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
||||||
let mut state = ForwardState::default();
|
let mut state = InterfaceForwardState::new(port_forward);
|
||||||
let mut interfaces = ip_info.read_and_mark_seen();
|
let mut interfaces = ip_info.read_and_mark_seen();
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
msg = req_recv.recv() => {
|
msg = req_recv.recv() => {
|
||||||
if let Some((msg, re)) = msg {
|
if let Some(cmd) = msg {
|
||||||
if let Some(req) = msg {
|
match cmd {
|
||||||
re.send(state.handle_request(req, &interfaces).await).ok();
|
InterfaceForwardCommand::Forward(req, re) => {
|
||||||
} else {
|
re.send(state.handle_request(req, &interfaces).await).ok()
|
||||||
re.send(state.sync(&interfaces).await).ok();
|
}
|
||||||
}
|
InterfaceForwardCommand::Sync(re) => {
|
||||||
|
re.send(state.sync(&interfaces).await).ok()
|
||||||
|
}
|
||||||
|
InterfaceForwardCommand::DumpTable(re) => {
|
||||||
|
re.send((&state).into()).ok()
|
||||||
|
}
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -242,45 +542,56 @@ impl PortForwardController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
req: req_send,
|
req: req_send,
|
||||||
_thread: thread,
|
_thread: thread,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add(
|
pub async fn add(
|
||||||
&self,
|
&self,
|
||||||
external: u16,
|
external: u16,
|
||||||
filter: impl InterfaceFilter,
|
filter: DynInterfaceFilter,
|
||||||
target: SocketAddr,
|
target: SocketAddrV4,
|
||||||
) -> Result<Arc<()>, Error> {
|
) -> Result<Arc<()>, Error> {
|
||||||
let rc = Arc::new(());
|
let rc = Arc::new(());
|
||||||
let (send, recv) = oneshot::channel();
|
let (send, recv) = oneshot::channel();
|
||||||
self.req
|
self.req
|
||||||
.send((
|
.send(InterfaceForwardCommand::Forward(
|
||||||
Some(ForwardRequest {
|
InterfaceForwardRequest {
|
||||||
external,
|
external,
|
||||||
target,
|
target,
|
||||||
filter: filter.into_dyn(),
|
filter,
|
||||||
rc: Arc::downgrade(&rc),
|
rc,
|
||||||
}),
|
},
|
||||||
send,
|
send,
|
||||||
))
|
))
|
||||||
.map_err(err_has_exited)?;
|
.map_err(err_has_exited)?;
|
||||||
|
|
||||||
recv.await.map_err(err_has_exited)?.map(|_| rc)
|
recv.await.map_err(err_has_exited)?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn gc(&self) -> Result<(), Error> {
|
pub async fn gc(&self) -> Result<(), Error> {
|
||||||
let (send, recv) = oneshot::channel();
|
let (send, recv) = oneshot::channel();
|
||||||
self.req.send((None, send)).map_err(err_has_exited)?;
|
self.req
|
||||||
|
.send(InterfaceForwardCommand::Sync(send))
|
||||||
|
.map_err(err_has_exited)?;
|
||||||
|
|
||||||
recv.await.map_err(err_has_exited)?
|
recv.await.map_err(err_has_exited)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn dump_table(&self) -> Result<ForwardTable, Error> {
|
||||||
|
let (req, res) = oneshot::channel();
|
||||||
|
self.req
|
||||||
|
.send(InterfaceForwardCommand::DumpTable(req))
|
||||||
|
.map_err(err_has_exited)?;
|
||||||
|
res.await.map_err(err_has_exited)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn forward(interface: &str, source: SocketAddr, target: SocketAddr) -> Result<(), Error> {
|
async fn forward(source: SocketAddrV4, target: SocketAddrV4) -> Result<(), Error> {
|
||||||
Command::new("/usr/lib/startos/scripts/forward-port")
|
Command::new("/usr/lib/startos/scripts/forward-port")
|
||||||
.env("iiface", interface)
|
|
||||||
.env("oiface", START9_BRIDGE_IFACE)
|
|
||||||
.env("sip", source.ip().to_string())
|
.env("sip", source.ip().to_string())
|
||||||
.env("dip", target.ip().to_string())
|
.env("dip", target.ip().to_string())
|
||||||
.env("sport", source.port().to_string())
|
.env("sport", source.port().to_string())
|
||||||
@@ -290,11 +601,9 @@ async fn forward(interface: &str, source: SocketAddr, target: SocketAddr) -> Res
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unforward(interface: &str, source: SocketAddr, target: SocketAddr) -> Result<(), Error> {
|
async fn unforward(source: SocketAddrV4, target: SocketAddrV4) -> Result<(), Error> {
|
||||||
Command::new("/usr/lib/startos/scripts/forward-port")
|
Command::new("/usr/lib/startos/scripts/forward-port")
|
||||||
.env("UNDO", "1")
|
.env("UNDO", "1")
|
||||||
.env("iiface", interface)
|
|
||||||
.env("oiface", START9_BRIDGE_IFACE)
|
|
||||||
.env("sip", source.ip().to_string())
|
.env("sip", source.ip().to_string())
|
||||||
.env("dip", target.ip().to_string())
|
.env("dip", target.ip().to_string())
|
||||||
.env("sport", source.port().to_string())
|
.env("sport", source.port().to_string())
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
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};
|
||||||
use std::task::Poll;
|
use std::task::{Poll, ready};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use futures::future::Either;
|
||||||
use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
|
use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
use imbl::{OrdMap, OrdSet};
|
use imbl::{OrdMap, OrdSet};
|
||||||
@@ -16,33 +18,34 @@ use itertools::Itertools;
|
|||||||
use models::GatewayId;
|
use models::GatewayId;
|
||||||
use nix::net::if_::if_nametoindex;
|
use nix::net::if_::if_nametoindex;
|
||||||
use patch_db::json_ptr::JsonPointer;
|
use patch_db::json_ptr::JsonPointer;
|
||||||
use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler};
|
use rpc_toolkit::{Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::TcpListener;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
use visit_rs::{Visit, VisitFields};
|
||||||
use zbus::proxy::{PropertyChanged, PropertyStream, SignalStream};
|
use zbus::proxy::{PropertyChanged, PropertyStream, SignalStream};
|
||||||
use zbus::zvariant::{
|
use zbus::zvariant::{
|
||||||
DeserializeDict, Dict, OwnedObjectPath, OwnedValue, Type as ZType, Value as ZValue,
|
DeserializeDict, Dict, OwnedObjectPath, OwnedValue, Type as ZType, Value as ZValue,
|
||||||
};
|
};
|
||||||
use zbus::{proxy, Connection};
|
use zbus::{Connection, proxy};
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::public::{IpInfo, NetworkInterfaceInfo, NetworkInterfaceType};
|
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
|
use crate::db::model::public::{IpInfo, NetworkInterfaceInfo, NetworkInterfaceType};
|
||||||
use crate::net::forward::START9_BRIDGE_IFACE;
|
use crate::net::forward::START9_BRIDGE_IFACE;
|
||||||
use crate::net::gateway::device::DeviceProxy;
|
use crate::net::gateway::device::DeviceProxy;
|
||||||
use crate::net::utils::ipv6_is_link_local;
|
use crate::net::utils::ipv6_is_link_local;
|
||||||
use crate::net::web_server::Accept;
|
use crate::net::web_server::{Accept, AcceptStream, Acceptor, MetadataVisitor};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::util::Invoke;
|
||||||
use crate::util::collections::OrdMapIterMut;
|
use crate::util::collections::OrdMapIterMut;
|
||||||
use crate::util::future::Until;
|
use crate::util::future::Until;
|
||||||
use crate::util::io::open_file;
|
use crate::util::io::open_file;
|
||||||
use crate::util::serde::{display_serializable, HandlerExtSerde};
|
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||||
use crate::util::sync::{SyncMutex, Watch};
|
use crate::util::sync::{SyncMutex, Watch};
|
||||||
use crate::util::Invoke;
|
|
||||||
|
|
||||||
pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
pub fn gateway_api<C: Context>() -> ParentHandler<C> {
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
@@ -128,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>,
|
||||||
@@ -145,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,
|
||||||
}
|
}
|
||||||
@@ -161,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,
|
||||||
}
|
}
|
||||||
@@ -174,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,
|
||||||
@@ -244,6 +243,8 @@ mod active_connection {
|
|||||||
default_service = "org.freedesktop.NetworkManager"
|
default_service = "org.freedesktop.NetworkManager"
|
||||||
)]
|
)]
|
||||||
trait ConnectionSettings {
|
trait ConnectionSettings {
|
||||||
|
fn delete(&self) -> Result<(), Error>;
|
||||||
|
|
||||||
fn get_settings(&self) -> Result<HashMap<String, HashMap<String, OwnedValue>>, Error>;
|
fn get_settings(&self) -> Result<HashMap<String, HashMap<String, OwnedValue>>, Error>;
|
||||||
|
|
||||||
fn update2(
|
fn update2(
|
||||||
@@ -400,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?;
|
||||||
@@ -432,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 {
|
||||||
@@ -583,15 +595,12 @@ async fn watch_ip(
|
|||||||
loop {
|
loop {
|
||||||
until
|
until
|
||||||
.run(async {
|
.run(async {
|
||||||
let external = active_connection_proxy.state_flags().await? & 0x80 != 0;
|
|
||||||
if external {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let device_type = match device_proxy.device_type().await? {
|
let device_type = match device_proxy.device_type().await? {
|
||||||
1 => Some(NetworkInterfaceType::Ethernet),
|
1 => Some(NetworkInterfaceType::Ethernet),
|
||||||
2 => Some(NetworkInterfaceType::Wireless),
|
2 => Some(NetworkInterfaceType::Wireless),
|
||||||
|
13 => Some(NetworkInterfaceType::Bridge),
|
||||||
29 => Some(NetworkInterfaceType::Wireguard),
|
29 => Some(NetworkInterfaceType::Wireguard),
|
||||||
|
32 => Some(NetworkInterfaceType::Loopback),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -671,7 +680,14 @@ async fn watch_ip(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(IpNet::try_from)
|
.map(IpNet::try_from)
|
||||||
.try_collect()?;
|
.try_collect()?;
|
||||||
let wan_ip = if !subnets.is_empty() {
|
let wan_ip = if !subnets.is_empty()
|
||||||
|
&& !matches!(
|
||||||
|
device_type,
|
||||||
|
Some(
|
||||||
|
NetworkInterfaceType::Bridge
|
||||||
|
| NetworkInterfaceType::Loopback
|
||||||
|
)
|
||||||
|
) {
|
||||||
match get_wan_ipv4(iface.as_str()).await {
|
match get_wan_ipv4(iface.as_str()).await {
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -711,6 +727,7 @@ async fn watch_ip(
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
ip_info.wan_ip = ip_info.wan_ip.or(prev_wan_ip);
|
ip_info.wan_ip = ip_info.wan_ip.or(prev_wan_ip);
|
||||||
|
let ip_info = Arc::new(ip_info);
|
||||||
m.insert(
|
m.insert(
|
||||||
iface.clone(),
|
iface.clone(),
|
||||||
NetworkInterfaceInfo {
|
NetworkInterfaceInfo {
|
||||||
@@ -785,13 +802,7 @@ impl NetworkInterfaceWatcher {
|
|||||||
watch_activated: impl IntoIterator<Item = GatewayId>,
|
watch_activated: impl IntoIterator<Item = GatewayId>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ip_info = Watch::new(OrdMap::new());
|
let ip_info = Watch::new(OrdMap::new());
|
||||||
let activated = Watch::new(
|
let activated = Watch::new(watch_activated.into_iter().map(|k| (k, false)).collect());
|
||||||
watch_activated
|
|
||||||
.into_iter()
|
|
||||||
.chain([NetworkInterfaceInfo::lxc_bridge().0.clone()])
|
|
||||||
.map(|k| (k, false))
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
Self {
|
Self {
|
||||||
activated: activated.clone(),
|
activated: activated.clone(),
|
||||||
ip_info: ip_info.clone(),
|
ip_info: ip_info.clone(),
|
||||||
@@ -831,7 +842,7 @@ impl NetworkInterfaceWatcher {
|
|||||||
self.ip_info.read()
|
self.ip_info.read()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind(&self, port: u16) -> Result<NetworkInterfaceListener, Error> {
|
pub fn bind<B: Bind>(&self, bind: B, port: u16) -> Result<NetworkInterfaceListener<B>, Error> {
|
||||||
let arc = Arc::new(());
|
let arc = Arc::new(());
|
||||||
self.listeners.mutate(|l| {
|
self.listeners.mutate(|l| {
|
||||||
if l.get(&port).filter(|w| w.strong_count() > 0).is_some() {
|
if l.get(&port).filter(|w| w.strong_count() > 0).is_some() {
|
||||||
@@ -844,22 +855,20 @@ impl NetworkInterfaceWatcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
let ip_info = self.ip_info.clone_unseen();
|
let ip_info = self.ip_info.clone_unseen();
|
||||||
let activated = self.activated.clone_unseen();
|
|
||||||
Ok(NetworkInterfaceListener {
|
Ok(NetworkInterfaceListener {
|
||||||
_arc: arc,
|
_arc: arc,
|
||||||
ip_info,
|
ip_info,
|
||||||
activated,
|
listeners: ListenerMap::new(bind, port),
|
||||||
listeners: ListenerMap::new(port),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upgrade_listener(
|
pub fn upgrade_listener<B: Bind>(
|
||||||
&self,
|
&self,
|
||||||
SelfContainedNetworkInterfaceListener {
|
SelfContainedNetworkInterfaceListener {
|
||||||
mut listener,
|
mut listener,
|
||||||
..
|
..
|
||||||
}: SelfContainedNetworkInterfaceListener,
|
}: SelfContainedNetworkInterfaceListener<B>,
|
||||||
) -> Result<NetworkInterfaceListener, Error> {
|
) -> Result<NetworkInterfaceListener<B>, Error> {
|
||||||
let port = listener.listeners.port;
|
let port = listener.listeners.port;
|
||||||
let arc = &listener._arc;
|
let arc = &listener._arc;
|
||||||
self.listeners.mutate(|l| {
|
self.listeners.mutate(|l| {
|
||||||
@@ -1095,7 +1104,7 @@ impl NetworkInterfaceController {
|
|||||||
.ip_info
|
.ip_info
|
||||||
.peek(|ifaces| ifaces.get(interface).map(|i| i.ip_info.is_some()))
|
.peek(|ifaces| ifaces.get(interface).map(|i| i.ip_info.is_some()))
|
||||||
else {
|
else {
|
||||||
return Ok(());
|
return self.forget(interface).await;
|
||||||
};
|
};
|
||||||
|
|
||||||
if has_ip_info {
|
if has_ip_info {
|
||||||
@@ -1115,7 +1124,21 @@ impl NetworkInterfaceController {
|
|||||||
|
|
||||||
let device_proxy = DeviceProxy::new(&connection, device).await?;
|
let device_proxy = DeviceProxy::new(&connection, device).await?;
|
||||||
|
|
||||||
device_proxy.delete().await?;
|
let ac = device_proxy.active_connection().await?;
|
||||||
|
|
||||||
|
if &*ac == "/" {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("Cannot delete device without active connection"),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let ac_proxy = active_connection::ActiveConnectionProxy::new(&connection, ac).await?;
|
||||||
|
|
||||||
|
let settings =
|
||||||
|
ConnectionSettingsProxy::new(&connection, ac_proxy.connection().await?).await?;
|
||||||
|
|
||||||
|
settings.delete().await?;
|
||||||
|
|
||||||
ip_info
|
ip_info
|
||||||
.wait_for(|ifaces| ifaces.get(interface).map_or(true, |i| i.ip_info.is_none()))
|
.wait_for(|ifaces| ifaces.get(interface).map_or(true, |i| i.ip_info.is_none()))
|
||||||
@@ -1158,45 +1181,6 @@ impl NetworkInterfaceController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ListenerMap {
|
|
||||||
prev_filter: DynInterfaceFilter,
|
|
||||||
port: u16,
|
|
||||||
listeners: BTreeMap<SocketAddr, (TcpListener, Option<Ipv4Addr>)>,
|
|
||||||
}
|
|
||||||
impl ListenerMap {
|
|
||||||
fn from_listener(listener: impl IntoIterator<Item = TcpListener>) -> Result<Self, Error> {
|
|
||||||
let mut port = 0;
|
|
||||||
let mut listeners = BTreeMap::<SocketAddr, (TcpListener, Option<Ipv4Addr>)>::new();
|
|
||||||
for listener in listener {
|
|
||||||
let mut local = listener.local_addr().with_kind(ErrorKind::Network)?;
|
|
||||||
if let SocketAddr::V6(l) = &mut local {
|
|
||||||
if ipv6_is_link_local(*l.ip()) && l.scope_id() == 0 {
|
|
||||||
continue; // TODO determine scope id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if port != 0 && port != local.port() {
|
|
||||||
return Err(Error::new(
|
|
||||||
eyre!("Provided listeners are bound to different ports"),
|
|
||||||
ErrorKind::InvalidRequest,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
port = local.port();
|
|
||||||
listeners.insert(local, (listener, None));
|
|
||||||
}
|
|
||||||
if port == 0 {
|
|
||||||
return Err(Error::new(
|
|
||||||
eyre!("Listener array cannot be empty"),
|
|
||||||
ErrorKind::InvalidRequest,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
prev_filter: false.into_dyn(),
|
|
||||||
port,
|
|
||||||
listeners,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait InterfaceFilter: Any + Clone + std::fmt::Debug + Eq + Ord + Send + Sync {
|
pub trait InterfaceFilter: Any + Clone + std::fmt::Debug + Eq + Ord + Send + Sync {
|
||||||
fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool;
|
fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool;
|
||||||
fn eq(&self, other: &dyn Any) -> bool {
|
fn eq(&self, other: &dyn Any) -> bool {
|
||||||
@@ -1224,6 +1208,14 @@ impl InterfaceFilter for bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct TypeFilter(pub NetworkInterfaceType);
|
||||||
|
impl InterfaceFilter for TypeFilter {
|
||||||
|
fn filter(&self, _: &GatewayId, info: &NetworkInterfaceInfo) -> bool {
|
||||||
|
info.ip_info.as_ref().and_then(|i| i.device_type) == Some(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct IdFilter(pub GatewayId);
|
pub struct IdFilter(pub GatewayId);
|
||||||
impl InterfaceFilter for IdFilter {
|
impl InterfaceFilter for IdFilter {
|
||||||
@@ -1329,6 +1321,9 @@ impl InterfaceFilter for DynInterfaceFilter {
|
|||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self.0.as_any()
|
self.0.as_any()
|
||||||
}
|
}
|
||||||
|
fn into_dyn(self) -> DynInterfaceFilter {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl DynInterfaceFilter {
|
impl DynInterfaceFilter {
|
||||||
fn new<T: InterfaceFilter>(value: T) -> Self {
|
fn new<T: InterfaceFilter>(value: T) -> Self {
|
||||||
@@ -1352,10 +1347,17 @@ impl Ord for DynInterfaceFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListenerMap {
|
struct ListenerMap<B: Bind> {
|
||||||
fn new(port: u16) -> Self {
|
prev_filter: DynInterfaceFilter,
|
||||||
|
bind: B,
|
||||||
|
port: u16,
|
||||||
|
listeners: BTreeMap<SocketAddr, B::Accept>,
|
||||||
|
}
|
||||||
|
impl<B: Bind> ListenerMap<B> {
|
||||||
|
fn new(bind: B, port: u16) -> Self {
|
||||||
Self {
|
Self {
|
||||||
prev_filter: false.into_dyn(),
|
prev_filter: false.into_dyn(),
|
||||||
|
bind,
|
||||||
port,
|
port,
|
||||||
listeners: BTreeMap::new(),
|
listeners: BTreeMap::new(),
|
||||||
}
|
}
|
||||||
@@ -1365,14 +1367,11 @@ impl ListenerMap {
|
|||||||
fn update(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
lxc_bridge: bool,
|
|
||||||
filter: &impl InterfaceFilter,
|
filter: &impl InterfaceFilter,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut keep = BTreeSet::<SocketAddr>::new();
|
let mut keep = BTreeSet::<SocketAddr>::new();
|
||||||
for (_, info) in ip_info
|
for (_, info) in ip_info
|
||||||
.iter()
|
.iter()
|
||||||
.chain([NetworkInterfaceInfo::loopback()])
|
|
||||||
.chain(Some(NetworkInterfaceInfo::lxc_bridge()).filter(|_| lxc_bridge))
|
|
||||||
.filter(|(id, info)| filter.filter(*id, *info))
|
.filter(|(id, info)| filter.filter(*id, *info))
|
||||||
{
|
{
|
||||||
if let Some(ip_info) = &info.ip_info {
|
if let Some(ip_info) = &info.ip_info {
|
||||||
@@ -1392,24 +1391,9 @@ impl ListenerMap {
|
|||||||
ip => SocketAddr::new(ip, self.port),
|
ip => SocketAddr::new(ip, self.port),
|
||||||
};
|
};
|
||||||
keep.insert(addr);
|
keep.insert(addr);
|
||||||
if let Some((_, wan_ip)) = self.listeners.get_mut(&addr) {
|
if !self.listeners.contains_key(&addr) {
|
||||||
*wan_ip = info.ip_info.as_ref().and_then(|i| i.wan_ip);
|
self.listeners.insert(addr, self.bind.bind(addr)?);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
self.listeners.insert(
|
|
||||||
addr,
|
|
||||||
(
|
|
||||||
TcpListener::from_std(
|
|
||||||
mio::net::TcpListener::bind(addr)
|
|
||||||
.with_ctx(|_| {
|
|
||||||
(ErrorKind::Network, lazy_format!("binding to {addr:?}"))
|
|
||||||
})?
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
.with_kind(ErrorKind::Network)?,
|
|
||||||
info.ip_info.as_ref().and_then(|i| i.wan_ip),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1417,24 +1401,13 @@ impl ListenerMap {
|
|||||||
self.prev_filter = filter.clone().into_dyn();
|
self.prev_filter = filter.clone().into_dyn();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn poll_accept(&self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>> {
|
fn poll_accept(
|
||||||
for (bind_addr, (listener, wan_ip)) in self.listeners.iter() {
|
&mut self,
|
||||||
if let Poll::Ready((stream, addr)) = listener.poll_accept(cx)? {
|
cx: &mut std::task::Context<'_>,
|
||||||
if let Err(e) = socket2::SockRef::from(&stream).set_tcp_keepalive(
|
) -> Poll<Result<(SocketAddr, <B::Accept as Accept>::Metadata, AcceptStream), Error>> {
|
||||||
&socket2::TcpKeepalive::new()
|
for (addr, listener) in self.listeners.iter_mut() {
|
||||||
.with_time(Duration::from_secs(900))
|
if let Poll::Ready((metadata, stream)) = listener.poll_accept(cx)? {
|
||||||
.with_interval(Duration::from_secs(60))
|
return Poll::Ready(Ok((*addr, metadata, stream)));
|
||||||
.with_retries(5),
|
|
||||||
) {
|
|
||||||
tracing::error!("Failed to set tcp keepalive: {e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
return Poll::Ready(Ok(Accepted {
|
|
||||||
stream,
|
|
||||||
peer: addr,
|
|
||||||
wan_ip: *wan_ip,
|
|
||||||
bind: *bind_addr,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
@@ -1445,65 +1418,102 @@ pub fn lookup_info_by_addr(
|
|||||||
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
) -> Option<(&GatewayId, &NetworkInterfaceInfo)> {
|
) -> Option<(&GatewayId, &NetworkInterfaceInfo)> {
|
||||||
ip_info
|
ip_info.iter().find(|(_, i)| {
|
||||||
.iter()
|
i.ip_info
|
||||||
.chain([
|
.as_ref()
|
||||||
NetworkInterfaceInfo::loopback(),
|
.map_or(false, |i| i.subnets.iter().any(|i| i.addr() == addr.ip()))
|
||||||
NetworkInterfaceInfo::lxc_bridge(),
|
})
|
||||||
])
|
|
||||||
.find(|(_, i)| {
|
|
||||||
i.ip_info
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |i| i.subnets.iter().any(|i| i.addr() == addr.ip()))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NetworkInterfaceListener {
|
pub trait Bind {
|
||||||
|
type Accept: Accept;
|
||||||
|
fn bind(&mut self, addr: SocketAddr) -> Result<Self::Accept, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct BindTcp;
|
||||||
|
impl Bind for BindTcp {
|
||||||
|
type Accept = TcpListener;
|
||||||
|
fn bind(&mut self, addr: SocketAddr) -> Result<Self::Accept, Error> {
|
||||||
|
TcpListener::from_std(
|
||||||
|
mio::net::TcpListener::bind(addr)
|
||||||
|
.with_kind(ErrorKind::Network)?
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
.with_kind(ErrorKind::Network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FromGatewayInfo {
|
||||||
|
fn from_gateway_info(id: &GatewayId, info: &NetworkInterfaceInfo) -> Self;
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct GatewayInfo {
|
||||||
|
pub id: GatewayId,
|
||||||
|
pub info: NetworkInterfaceInfo,
|
||||||
|
}
|
||||||
|
impl<V: MetadataVisitor> Visit<V> for GatewayInfo {
|
||||||
|
fn visit(&self, visitor: &mut V) -> <V as visit_rs::Visitor>::Result {
|
||||||
|
visitor.visit(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FromGatewayInfo for GatewayInfo {
|
||||||
|
fn from_gateway_info(id: &GatewayId, info: &NetworkInterfaceInfo) -> Self {
|
||||||
|
Self {
|
||||||
|
id: id.clone(),
|
||||||
|
info: info.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NetworkInterfaceListener<B: Bind = BindTcp> {
|
||||||
pub ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
pub ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
||||||
activated: Watch<BTreeMap<GatewayId, bool>>,
|
listeners: ListenerMap<B>,
|
||||||
listeners: ListenerMap,
|
|
||||||
_arc: Arc<()>,
|
_arc: Arc<()>,
|
||||||
}
|
}
|
||||||
impl NetworkInterfaceListener {
|
impl<B: Bind> NetworkInterfaceListener<B> {
|
||||||
pub fn port(&self) -> u16 {
|
|
||||||
self.listeners.port
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "unstable", inline(never))]
|
|
||||||
pub fn poll_accept(
|
|
||||||
&mut self,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
filter: &impl InterfaceFilter,
|
|
||||||
) -> Poll<Result<Accepted, Error>> {
|
|
||||||
while self.ip_info.poll_changed(cx).is_ready()
|
|
||||||
|| self.activated.poll_changed(cx).is_ready()
|
|
||||||
|| !DynInterfaceFilterT::eq(&self.listeners.prev_filter, filter.as_any())
|
|
||||||
{
|
|
||||||
let lxc_bridge = self.activated.peek(|a| {
|
|
||||||
a.get(NetworkInterfaceInfo::lxc_bridge().0)
|
|
||||||
.copied()
|
|
||||||
.unwrap_or_default()
|
|
||||||
});
|
|
||||||
self.ip_info
|
|
||||||
.peek_and_mark_seen(|ip_info| self.listeners.update(ip_info, lxc_bridge, filter))?;
|
|
||||||
}
|
|
||||||
self.listeners.poll_accept(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
mut ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
mut ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
||||||
activated: Watch<BTreeMap<GatewayId, bool>>,
|
bind: B,
|
||||||
port: u16,
|
port: u16,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ip_info.mark_unseen();
|
ip_info.mark_unseen();
|
||||||
Self {
|
Self {
|
||||||
ip_info,
|
ip_info,
|
||||||
activated,
|
listeners: ListenerMap::new(bind, port),
|
||||||
listeners: ListenerMap::new(port),
|
|
||||||
_arc: Arc::new(()),
|
_arc: Arc::new(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn port(&self) -> u16 {
|
||||||
|
self.listeners.port
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "unstable", inline(never))]
|
||||||
|
pub fn poll_accept<M: FromGatewayInfo>(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
filter: &impl InterfaceFilter,
|
||||||
|
) -> Poll<Result<(M, <B::Accept as Accept>::Metadata, AcceptStream), Error>> {
|
||||||
|
while self.ip_info.poll_changed(cx).is_ready()
|
||||||
|
|| !DynInterfaceFilterT::eq(&self.listeners.prev_filter, filter.as_any())
|
||||||
|
{
|
||||||
|
self.ip_info
|
||||||
|
.peek_and_mark_seen(|ip_info| self.listeners.update(ip_info, filter))?;
|
||||||
|
}
|
||||||
|
let (addr, inner, stream) = ready!(self.listeners.poll_accept(cx)?);
|
||||||
|
Poll::Ready(Ok((
|
||||||
|
self.ip_info
|
||||||
|
.peek(|ip_info| {
|
||||||
|
lookup_info_by_addr(ip_info, addr)
|
||||||
|
.map(|(id, info)| M::from_gateway_info(id, info))
|
||||||
|
})
|
||||||
|
.or_not_found(lazy_format!("gateway for {addr}"))?,
|
||||||
|
inner,
|
||||||
|
stream,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn change_ip_info_source(
|
pub fn change_ip_info_source(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
mut ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
||||||
@@ -1512,7 +1522,10 @@ impl NetworkInterfaceListener {
|
|||||||
self.ip_info = ip_info;
|
self.ip_info = ip_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn accept(&mut self, filter: &impl InterfaceFilter) -> Result<Accepted, Error> {
|
pub async fn accept<M: FromGatewayInfo>(
|
||||||
|
&mut self,
|
||||||
|
filter: &impl InterfaceFilter,
|
||||||
|
) -> Result<(M, <B::Accept as Accept>::Metadata, AcceptStream), Error> {
|
||||||
futures::future::poll_fn(|cx| self.poll_accept(cx, filter)).await
|
futures::future::poll_fn(|cx| self.poll_accept(cx, filter)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1528,37 +1541,128 @@ impl NetworkInterfaceListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Accepted {
|
#[derive(VisitFields)]
|
||||||
pub stream: TcpStream,
|
pub struct NetworkInterfaceListenerAcceptMetadata<B: Bind> {
|
||||||
pub peer: SocketAddr,
|
pub inner: <B::Accept as Accept>::Metadata,
|
||||||
pub wan_ip: Option<Ipv4Addr>,
|
pub info: GatewayInfo,
|
||||||
pub bind: SocketAddr,
|
|
||||||
}
|
}
|
||||||
|
impl<B: Bind> fmt::Debug for NetworkInterfaceListenerAcceptMetadata<B> {
|
||||||
pub struct SelfContainedNetworkInterfaceListener {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
_watch_thread: NonDetachingJoinHandle<()>,
|
f.debug_struct("NetworkInterfaceListenerAcceptMetadata")
|
||||||
listener: NetworkInterfaceListener,
|
.field("inner", &self.inner)
|
||||||
|
.field("info", &self.info)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl SelfContainedNetworkInterfaceListener {
|
impl<B: Bind> Clone for NetworkInterfaceListenerAcceptMetadata<B>
|
||||||
pub fn bind(port: u16) -> Self {
|
where
|
||||||
let ip_info = Watch::new(OrdMap::new());
|
<B::Accept as Accept>::Metadata: Clone,
|
||||||
let activated = Watch::new(
|
{
|
||||||
[(NetworkInterfaceInfo::lxc_bridge().0.clone(), false)]
|
fn clone(&self) -> Self {
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
let _watch_thread = tokio::spawn(watcher(ip_info.clone(), activated.clone())).into();
|
|
||||||
Self {
|
Self {
|
||||||
_watch_thread,
|
inner: self.inner.clone(),
|
||||||
listener: NetworkInterfaceListener::new(ip_info, activated, port),
|
info: self.info.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Accept for SelfContainedNetworkInterfaceListener {
|
impl<B, V> Visit<V> for NetworkInterfaceListenerAcceptMetadata<B>
|
||||||
|
where
|
||||||
|
B: Bind,
|
||||||
|
<B::Accept as Accept>::Metadata: Visit<V> + Clone + Send + Sync + 'static,
|
||||||
|
V: MetadataVisitor,
|
||||||
|
{
|
||||||
|
fn visit(&self, visitor: &mut V) -> V::Result {
|
||||||
|
self.visit_fields(visitor).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Bind> Accept for NetworkInterfaceListener<B> {
|
||||||
|
type Metadata = NetworkInterfaceListenerAcceptMetadata<B>;
|
||||||
fn poll_accept(
|
fn poll_accept(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> std::task::Poll<Result<super::web_server::Accepted, Error>> {
|
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
|
||||||
|
NetworkInterfaceListener::poll_accept(self, cx, &true).map(|res| {
|
||||||
|
res.map(|(info, inner, stream)| {
|
||||||
|
(
|
||||||
|
NetworkInterfaceListenerAcceptMetadata { inner, info },
|
||||||
|
stream,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SelfContainedNetworkInterfaceListener<B: Bind = BindTcp> {
|
||||||
|
_watch_thread: NonDetachingJoinHandle<()>,
|
||||||
|
listener: NetworkInterfaceListener<B>,
|
||||||
|
}
|
||||||
|
impl<B: Bind> SelfContainedNetworkInterfaceListener<B> {
|
||||||
|
pub fn bind(bind: B, port: u16) -> Self {
|
||||||
|
let ip_info = Watch::new(OrdMap::new());
|
||||||
|
let _watch_thread =
|
||||||
|
tokio::spawn(watcher(ip_info.clone(), Watch::new(BTreeMap::new()))).into();
|
||||||
|
Self {
|
||||||
|
_watch_thread,
|
||||||
|
listener: NetworkInterfaceListener::new(ip_info, bind, port),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<B: Bind> Accept for SelfContainedNetworkInterfaceListener<B> {
|
||||||
|
type Metadata = <NetworkInterfaceListener<B> as Accept>::Metadata;
|
||||||
|
fn poll_accept(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Result<(Self::Metadata, AcceptStream), Error>> {
|
||||||
Accept::poll_accept(&mut self.listener, cx)
|
Accept::poll_accept(&mut self.listener, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type UpgradableListener<B = BindTcp> =
|
||||||
|
Option<Either<SelfContainedNetworkInterfaceListener<B>, NetworkInterfaceListener<B>>>;
|
||||||
|
|
||||||
|
impl<B> Acceptor<UpgradableListener<B>>
|
||||||
|
where
|
||||||
|
B: Bind + Send + Sync + 'static,
|
||||||
|
B::Accept: Send + Sync,
|
||||||
|
{
|
||||||
|
pub fn bind_upgradable(listener: SelfContainedNetworkInterfaceListener<B>) -> Self {
|
||||||
|
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(),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ use std::net::Ipv4Addr;
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use models::GatewayId;
|
use models::GatewayId;
|
||||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::net::acme::AcmeProvider;
|
use crate::net::acme::AcmeProvider;
|
||||||
use crate::net::host::{all_hosts, HostApiKind};
|
use crate::net::host::{HostApiKind, all_hosts};
|
||||||
use crate::net::tor::OnionAddress;
|
use crate::net::tor::OnionAddress;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::{display_serializable, HandlerExtSerde};
|
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
@@ -105,8 +105,8 @@ fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn address_api<C: Context, Kind: HostApiKind>(
|
pub fn address_api<C: Context, Kind: HostApiKind>()
|
||||||
) -> ParentHandler<C, Kind::Params, Kind::InheritedParams> {
|
-> ParentHandler<C, Kind::Params, Kind::InheritedParams> {
|
||||||
ParentHandler::<C, Kind::Params, Kind::InheritedParams>::new()
|
ParentHandler::<C, Kind::Params, Kind::InheritedParams>::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"domain",
|
"domain",
|
||||||
@@ -357,15 +357,7 @@ pub async fn add_onion<Kind: HostApiKind>(
|
|||||||
OnionParams { onion }: OnionParams,
|
OnionParams { onion }: OnionParams,
|
||||||
inheritance: Kind::Inheritance,
|
inheritance: Kind::Inheritance,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let onion = onion
|
let onion = onion.parse::<OnionAddress>()?;
|
||||||
.strip_suffix(".onion")
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::new(
|
|
||||||
eyre!("onion hostname must end in .onion"),
|
|
||||||
ErrorKind::InvalidOnionAddress,
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.parse::<OnionAddress>()?;
|
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
db.as_private().as_key_store().as_onion().get_key(&onion)?;
|
db.as_private().as_key_store().as_onion().get_key(&onion)?;
|
||||||
@@ -388,15 +380,7 @@ pub async fn remove_onion<Kind: HostApiKind>(
|
|||||||
OnionParams { onion }: OnionParams,
|
OnionParams { onion }: OnionParams,
|
||||||
inheritance: Kind::Inheritance,
|
inheritance: Kind::Inheritance,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let onion = onion
|
let onion = onion.parse::<OnionAddress>()?;
|
||||||
.strip_suffix(".onion")
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::new(
|
|
||||||
eyre!("onion hostname must end in .onion"),
|
|
||||||
ErrorKind::InvalidOnionAddress,
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.parse::<OnionAddress>()?;
|
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
Kind::host_for(&inheritance, db)?
|
Kind::host_for(&inheritance, db)?
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use clap::builder::ValueParserFactory;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use clap::builder::ValueParserFactory;
|
||||||
use imbl::OrdSet;
|
use imbl::OrdSet;
|
||||||
use models::{FromStrParser, GatewayId, HostId};
|
use models::{FromStrParser, GatewayId, HostId};
|
||||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
use rpc_toolkit::{Context, Empty, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ use crate::net::gateway::InterfaceFilter;
|
|||||||
use crate::net::host::HostApiKind;
|
use crate::net::host::HostApiKind;
|
||||||
use crate::net::vhost::AlpnInfo;
|
use crate::net::vhost::AlpnInfo;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::{display_serializable, HandlerExtSerde};
|
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@@ -135,11 +135,12 @@ impl BindInfo {
|
|||||||
}
|
}
|
||||||
impl InterfaceFilter for NetInfo {
|
impl InterfaceFilter for NetInfo {
|
||||||
fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool {
|
fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool {
|
||||||
if info.public() {
|
info.ip_info.is_some()
|
||||||
self.public_enabled.contains(id)
|
&& if info.public() {
|
||||||
} else {
|
self.public_enabled.contains(id)
|
||||||
!self.private_disabled.contains(id)
|
} else {
|
||||||
}
|
!self.private_disabled.contains(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,8 +170,8 @@ pub struct AddSslOptions {
|
|||||||
pub alpn: Option<AlpnInfo>,
|
pub alpn: Option<AlpnInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn binding<C: Context, Kind: HostApiKind>(
|
pub fn binding<C: Context, Kind: HostApiKind>()
|
||||||
) -> ParentHandler<C, Kind::Params, Kind::InheritedParams> {
|
-> ParentHandler<C, Kind::Params, Kind::InheritedParams> {
|
||||||
ParentHandler::<C, Kind::Params, Kind::InheritedParams>::new()
|
ParentHandler::<C, Kind::Params, Kind::InheritedParams>::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"list",
|
"list",
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ use clap::Parser;
|
|||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::{HostId, PackageId};
|
use models::{HostId, PackageId};
|
||||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, OrEmpty, ParentHandler};
|
use rpc_toolkit::{Context, Empty, HandlerExt, OrEmpty, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::net::forward::AvailablePorts;
|
use crate::net::forward::AvailablePorts;
|
||||||
use crate::net::host::address::{address_api, HostAddress, PublicDomainConfig};
|
use crate::net::host::address::{HostAddress, PublicDomainConfig, address_api};
|
||||||
use crate::net::host::binding::{binding, BindInfo, BindOptions};
|
use crate::net::host::binding::{BindInfo, BindOptions, binding};
|
||||||
use crate::net::service_interface::HostnameInfo;
|
use crate::net::service_interface::HostnameInfo;
|
||||||
use crate::net::tor::OnionAddress;
|
use crate::net::tor::OnionAddress;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use crate::prelude::*;
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct KeyStore {
|
pub struct KeyStore {
|
||||||
pub onion: OnionStore,
|
pub onion: OnionStore,
|
||||||
pub local_certs: CertStore,
|
pub local_certs: CertStore,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub mod service_interface;
|
|||||||
pub mod socks;
|
pub mod socks;
|
||||||
pub mod ssl;
|
pub mod ssl;
|
||||||
pub mod static_server;
|
pub mod static_server;
|
||||||
|
pub mod tls;
|
||||||
pub mod tor;
|
pub mod tor;
|
||||||
pub mod tunnel;
|
pub mod tunnel;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
@@ -33,6 +34,10 @@ pub fn net_api<C: Context>() -> ParentHandler<C> {
|
|||||||
"dns",
|
"dns",
|
||||||
dns::dns_api::<C>().with_about("Manage and query DNS"),
|
dns::dns_api::<C>().with_about("Manage and query DNS"),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"forward",
|
||||||
|
forward::forward_api::<C>().with_about("Manage port forwards"),
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"gateway",
|
"gateway",
|
||||||
gateway::gateway_api::<C>().with_about("View and edit gateway configurations"),
|
gateway::gateway_api::<C>().with_about("View and edit gateway configurations"),
|
||||||
|
|||||||
@@ -1,46 +1,48 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::net::{Ipv4Addr, SocketAddr};
|
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use imbl::{vector, OrdMap};
|
use imbl::{OrdMap, vector};
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use ipnet::IpNet;
|
use ipnet::IpNet;
|
||||||
use models::{HostId, OptionExt, PackageId};
|
use models::{GatewayId, HostId, OptionExt, PackageId};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
use tokio_rustls::rustls::ClientConfig as TlsClientConfig;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType};
|
use crate::HOST_IP;
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
|
use crate::db::model::public::NetworkInterfaceType;
|
||||||
use crate::error::ErrorCollection;
|
use crate::error::ErrorCollection;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::Hostname;
|
||||||
use crate::net::dns::DnsController;
|
use crate::net::dns::DnsController;
|
||||||
use crate::net::forward::PortForwardController;
|
use crate::net::forward::{InterfacePortForwardController, START9_BRIDGE_IFACE};
|
||||||
use crate::net::gateway::{
|
use crate::net::gateway::{
|
||||||
AndFilter, DynInterfaceFilter, IdFilter, InterfaceFilter, NetworkInterfaceController, OrFilter,
|
AndFilter, DynInterfaceFilter, IdFilter, InterfaceFilter, NetworkInterfaceController, OrFilter,
|
||||||
PublicFilter, SecureFilter,
|
PublicFilter, SecureFilter, TypeFilter,
|
||||||
};
|
};
|
||||||
use crate::net::host::address::HostAddress;
|
use crate::net::host::address::HostAddress;
|
||||||
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
|
use crate::net::host::binding::{AddSslOptions, BindId, BindOptions};
|
||||||
use crate::net::host::{host_for, Host, Hosts};
|
use crate::net::host::{Host, Hosts, host_for};
|
||||||
use crate::net::service_interface::{GatewayInfo, HostnameInfo, IpHostname, OnionHostname};
|
use crate::net::service_interface::{GatewayInfo, HostnameInfo, IpHostname, OnionHostname};
|
||||||
use crate::net::socks::SocksController;
|
use crate::net::socks::SocksController;
|
||||||
use crate::net::tor::{OnionAddress, TorController, TorSecretKey};
|
use crate::net::tor::{OnionAddress, TorController, TorSecretKey};
|
||||||
use crate::net::utils::ipv6_is_local;
|
use crate::net::utils::ipv6_is_local;
|
||||||
use crate::net::vhost::{AlpnInfo, TargetInfo, VHostController};
|
use crate::net::vhost::{AlpnInfo, DynVHostTarget, ProxyTarget, VHostController};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::service::effects::callbacks::ServiceCallbacks;
|
use crate::service::effects::callbacks::ServiceCallbacks;
|
||||||
use crate::util::serde::MaybeUtf8String;
|
use crate::util::serde::MaybeUtf8String;
|
||||||
use crate::HOST_IP;
|
|
||||||
|
|
||||||
pub struct NetController {
|
pub struct NetController {
|
||||||
pub(crate) db: TypedPatchDb<Database>,
|
pub(crate) db: TypedPatchDb<Database>,
|
||||||
pub(super) tor: TorController,
|
pub(super) tor: TorController,
|
||||||
pub(super) vhost: VHostController,
|
pub(super) vhost: VHostController,
|
||||||
|
pub(super) tls_client_config: Arc<TlsClientConfig>,
|
||||||
pub(crate) net_iface: Arc<NetworkInterfaceController>,
|
pub(crate) net_iface: Arc<NetworkInterfaceController>,
|
||||||
pub(super) dns: DnsController,
|
pub(super) dns: DnsController,
|
||||||
pub(super) forward: PortForwardController,
|
pub(super) forward: InterfacePortForwardController,
|
||||||
pub(super) socks: SocksController,
|
pub(super) socks: SocksController,
|
||||||
pub(super) server_hostnames: Vec<Option<InternedString>>,
|
pub(super) server_hostnames: Vec<Option<InternedString>>,
|
||||||
pub(crate) callbacks: Arc<ServiceCallbacks>,
|
pub(crate) callbacks: Arc<ServiceCallbacks>,
|
||||||
@@ -55,12 +57,26 @@ impl NetController {
|
|||||||
let net_iface = Arc::new(NetworkInterfaceController::new(db.clone()));
|
let net_iface = Arc::new(NetworkInterfaceController::new(db.clone()));
|
||||||
let tor = TorController::new()?;
|
let tor = TorController::new()?;
|
||||||
let socks = SocksController::new(socks_listen, tor.clone())?;
|
let socks = SocksController::new(socks_listen, tor.clone())?;
|
||||||
|
let crypto_provider = Arc::new(tokio_rustls::rustls::crypto::ring::default_provider());
|
||||||
|
let tls_client_config = Arc::new(crate::net::tls::client_config(
|
||||||
|
crypto_provider.clone(),
|
||||||
|
[&*db
|
||||||
|
.peek()
|
||||||
|
.await
|
||||||
|
.as_private()
|
||||||
|
.as_key_store()
|
||||||
|
.as_local_certs()
|
||||||
|
.as_root_cert()
|
||||||
|
.de()?
|
||||||
|
.0],
|
||||||
|
)?);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
db: db.clone(),
|
db: db.clone(),
|
||||||
tor,
|
tor,
|
||||||
vhost: VHostController::new(db.clone(), net_iface.clone()),
|
vhost: VHostController::new(db.clone(), net_iface.clone(), crypto_provider),
|
||||||
|
tls_client_config,
|
||||||
dns: DnsController::init(db, &net_iface.watcher).await?,
|
dns: DnsController::init(db, &net_iface.watcher).await?,
|
||||||
forward: PortForwardController::new(net_iface.watcher.subscribe()),
|
forward: InterfacePortForwardController::new(net_iface.watcher.subscribe()),
|
||||||
net_iface,
|
net_iface,
|
||||||
socks,
|
socks,
|
||||||
server_hostnames: vec![
|
server_hostnames: vec![
|
||||||
@@ -133,8 +149,8 @@ impl NetController {
|
|||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
struct HostBinds {
|
struct HostBinds {
|
||||||
forwards: BTreeMap<u16, (SocketAddr, DynInterfaceFilter, Arc<()>)>,
|
forwards: BTreeMap<u16, (SocketAddrV4, DynInterfaceFilter, Arc<()>)>,
|
||||||
vhosts: BTreeMap<(Option<InternedString>, u16), (TargetInfo, Arc<()>)>,
|
vhosts: BTreeMap<(Option<InternedString>, u16), (ProxyTarget, Arc<()>)>,
|
||||||
private_dns: BTreeMap<InternedString, Arc<()>>,
|
private_dns: BTreeMap<InternedString, Arc<()>>,
|
||||||
tor: BTreeMap<OnionAddress, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>,
|
tor: BTreeMap<OnionAddress, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>,
|
||||||
}
|
}
|
||||||
@@ -225,8 +241,8 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn update(&mut self, ctrl: &NetController, id: HostId, host: Host) -> Result<(), Error> {
|
async fn update(&mut self, ctrl: &NetController, id: HostId, host: Host) -> Result<(), Error> {
|
||||||
let mut forwards: BTreeMap<u16, (SocketAddr, DynInterfaceFilter)> = BTreeMap::new();
|
let mut forwards: BTreeMap<u16, (SocketAddrV4, DynInterfaceFilter)> = BTreeMap::new();
|
||||||
let mut vhosts: BTreeMap<(Option<InternedString>, u16), TargetInfo> = BTreeMap::new();
|
let mut vhosts: BTreeMap<(Option<InternedString>, u16), ProxyTarget> = BTreeMap::new();
|
||||||
let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
|
let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
|
||||||
let mut tor: BTreeMap<OnionAddress, (TorSecretKey, OrdMap<u16, SocketAddr>)> =
|
let mut tor: BTreeMap<OnionAddress, (TorSecretKey, OrdMap<u16, SocketAddr>)> =
|
||||||
BTreeMap::new();
|
BTreeMap::new();
|
||||||
@@ -263,11 +279,13 @@ impl NetServiceData {
|
|||||||
for hostname in ctrl.server_hostnames.iter().cloned() {
|
for hostname in ctrl.server_hostnames.iter().cloned() {
|
||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(hostname, external),
|
(hostname, external),
|
||||||
TargetInfo {
|
ProxyTarget {
|
||||||
filter: bind.net.clone().into_dyn(),
|
filter: bind.net.clone().into_dyn(),
|
||||||
acme: None,
|
acme: None,
|
||||||
addr,
|
addr,
|
||||||
connect_ssl: connect_ssl.clone(),
|
connect_ssl: connect_ssl
|
||||||
|
.clone()
|
||||||
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -278,19 +296,19 @@ impl NetServiceData {
|
|||||||
if hostnames.insert(hostname.clone()) {
|
if hostnames.insert(hostname.clone()) {
|
||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(Some(hostname), external),
|
(Some(hostname), external),
|
||||||
TargetInfo {
|
ProxyTarget {
|
||||||
filter: OrFilter(
|
filter: OrFilter(
|
||||||
IdFilter(
|
TypeFilter(NetworkInterfaceType::Loopback),
|
||||||
NetworkInterfaceInfo::loopback().0.clone(),
|
IdFilter(GatewayId::from(InternedString::from(
|
||||||
),
|
START9_BRIDGE_IFACE,
|
||||||
IdFilter(
|
))),
|
||||||
NetworkInterfaceInfo::lxc_bridge().0.clone(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.into_dyn(),
|
.into_dyn(),
|
||||||
acme: None,
|
acme: None,
|
||||||
addr,
|
addr,
|
||||||
connect_ssl: connect_ssl.clone(),
|
connect_ssl: connect_ssl
|
||||||
|
.clone()
|
||||||
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
},
|
},
|
||||||
); // TODO: wrap onion ssl stream directly in tor ctrl
|
); // TODO: wrap onion ssl stream directly in tor ctrl
|
||||||
}
|
}
|
||||||
@@ -306,7 +324,7 @@ impl NetServiceData {
|
|||||||
if let Some(public) = &public {
|
if let Some(public) = &public {
|
||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(address.clone(), 5443),
|
(address.clone(), 5443),
|
||||||
TargetInfo {
|
ProxyTarget {
|
||||||
filter: AndFilter(
|
filter: AndFilter(
|
||||||
bind.net.clone(),
|
bind.net.clone(),
|
||||||
AndFilter(
|
AndFilter(
|
||||||
@@ -317,12 +335,14 @@ impl NetServiceData {
|
|||||||
.into_dyn(),
|
.into_dyn(),
|
||||||
acme: public.acme.clone(),
|
acme: public.acme.clone(),
|
||||||
addr,
|
addr,
|
||||||
connect_ssl: connect_ssl.clone(),
|
connect_ssl: connect_ssl
|
||||||
|
.clone()
|
||||||
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(address.clone(), 443),
|
(address.clone(), 443),
|
||||||
TargetInfo {
|
ProxyTarget {
|
||||||
filter: AndFilter(
|
filter: AndFilter(
|
||||||
bind.net.clone(),
|
bind.net.clone(),
|
||||||
if private {
|
if private {
|
||||||
@@ -342,13 +362,15 @@ impl NetServiceData {
|
|||||||
.into_dyn(),
|
.into_dyn(),
|
||||||
acme: public.acme.clone(),
|
acme: public.acme.clone(),
|
||||||
addr,
|
addr,
|
||||||
connect_ssl: connect_ssl.clone(),
|
connect_ssl: connect_ssl
|
||||||
|
.clone()
|
||||||
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(address.clone(), 443),
|
(address.clone(), 443),
|
||||||
TargetInfo {
|
ProxyTarget {
|
||||||
filter: AndFilter(
|
filter: AndFilter(
|
||||||
bind.net.clone(),
|
bind.net.clone(),
|
||||||
PublicFilter { public: false },
|
PublicFilter { public: false },
|
||||||
@@ -356,7 +378,9 @@ impl NetServiceData {
|
|||||||
.into_dyn(),
|
.into_dyn(),
|
||||||
acme: None,
|
acme: None,
|
||||||
addr,
|
addr,
|
||||||
connect_ssl: connect_ssl.clone(),
|
connect_ssl: connect_ssl
|
||||||
|
.clone()
|
||||||
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -364,7 +388,7 @@ impl NetServiceData {
|
|||||||
if let Some(public) = public {
|
if let Some(public) = public {
|
||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(address.clone(), external),
|
(address.clone(), external),
|
||||||
TargetInfo {
|
ProxyTarget {
|
||||||
filter: AndFilter(
|
filter: AndFilter(
|
||||||
bind.net.clone(),
|
bind.net.clone(),
|
||||||
if private {
|
if private {
|
||||||
@@ -381,13 +405,15 @@ impl NetServiceData {
|
|||||||
.into_dyn(),
|
.into_dyn(),
|
||||||
acme: public.acme.clone(),
|
acme: public.acme.clone(),
|
||||||
addr,
|
addr,
|
||||||
connect_ssl: connect_ssl.clone(),
|
connect_ssl: connect_ssl
|
||||||
|
.clone()
|
||||||
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(address.clone(), external),
|
(address.clone(), external),
|
||||||
TargetInfo {
|
ProxyTarget {
|
||||||
filter: AndFilter(
|
filter: AndFilter(
|
||||||
bind.net.clone(),
|
bind.net.clone(),
|
||||||
PublicFilter { public: false },
|
PublicFilter { public: false },
|
||||||
@@ -395,7 +421,9 @@ impl NetServiceData {
|
|||||||
.into_dyn(),
|
.into_dyn(),
|
||||||
acme: None,
|
acme: None,
|
||||||
addr,
|
addr,
|
||||||
connect_ssl: connect_ssl.clone(),
|
connect_ssl: connect_ssl
|
||||||
|
.clone()
|
||||||
|
.map(|_| ctrl.tls_client_config.clone()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -414,7 +442,7 @@ impl NetServiceData {
|
|||||||
forwards.insert(
|
forwards.insert(
|
||||||
external,
|
external,
|
||||||
(
|
(
|
||||||
(self.ip, *port).into(),
|
SocketAddrV4::new(self.ip, *port),
|
||||||
AndFilter(
|
AndFilter(
|
||||||
SecureFilter {
|
SecureFilter {
|
||||||
secure: bind.options.secure.is_some(),
|
secure: bind.options.secure.is_some(),
|
||||||
@@ -429,6 +457,11 @@ impl NetServiceData {
|
|||||||
hostname_info.remove(port).unwrap_or_default();
|
hostname_info.remove(port).unwrap_or_default();
|
||||||
for (gateway_id, info) in net_ifaces
|
for (gateway_id, info) in net_ifaces
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|(_, info)| {
|
||||||
|
info.ip_info.as_ref().map_or(false, |i| {
|
||||||
|
!matches!(i.device_type, Some(NetworkInterfaceType::Bridge))
|
||||||
|
})
|
||||||
|
})
|
||||||
.filter(|(id, info)| bind.net.filter(id, info))
|
.filter(|(id, info)| bind.net.filter(id, info))
|
||||||
{
|
{
|
||||||
let gateway = GatewayInfo {
|
let gateway = GatewayInfo {
|
||||||
@@ -653,7 +686,10 @@ impl NetServiceData {
|
|||||||
if let Some(prev) = prev {
|
if let Some(prev) = prev {
|
||||||
prev
|
prev
|
||||||
} else {
|
} else {
|
||||||
(target.clone(), ctrl.vhost.add(key.0, key.1, target)?)
|
(
|
||||||
|
target.clone(),
|
||||||
|
ctrl.vhost.add(key.0, key.1, DynVHostTarget::new(target))?,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -688,7 +724,7 @@ impl NetServiceData {
|
|||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeSet<_>>();
|
||||||
for onion in all {
|
for onion in all {
|
||||||
let mut prev = binds.tor.remove(&onion);
|
let mut prev = binds.tor.remove(&onion);
|
||||||
if let Some((key, tor_binds)) = tor.remove(&onion) {
|
if let Some((key, tor_binds)) = tor.remove(&onion).filter(|(_, b)| !b.is_empty()) {
|
||||||
prev = prev.filter(|(b, _)| b == &tor_binds);
|
prev = prev.filter(|(b, _)| b == &tor_binds);
|
||||||
binds.tor.insert(
|
binds.tor.insert(
|
||||||
onion,
|
onion,
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ use socks5_impl::server::auth::NoAuth;
|
|||||||
use socks5_impl::server::{AuthAdaptor, ClientConnection, Server};
|
use socks5_impl::server::{AuthAdaptor, ClientConnection, Server};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
|
use crate::HOST_IP;
|
||||||
use crate::net::tor::TorController;
|
use crate::net::tor::TorController;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::actor::background::BackgroundJobQueue;
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
use crate::HOST_IP;
|
|
||||||
|
|
||||||
pub const DEFAULT_SOCKS_LISTEN: SocketAddr = SocketAddr::V4(SocketAddrV4::new(
|
pub const DEFAULT_SOCKS_LISTEN: SocketAddr = SocketAddr::V4(SocketAddrV4::new(
|
||||||
Ipv4Addr::new(HOST_IP[0], HOST_IP[1], HOST_IP[2], HOST_IP[3]),
|
Ipv4Addr::new(HOST_IP[0], HOST_IP[1], HOST_IP[2], HOST_IP[3]),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::cmp::{Ordering, min};
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
@@ -10,22 +11,54 @@ use libc::time_t;
|
|||||||
use openssl::asn1::{Asn1Integer, Asn1Time, Asn1TimeRef};
|
use openssl::asn1::{Asn1Integer, Asn1Time, Asn1TimeRef};
|
||||||
use openssl::bn::{BigNum, MsbOption};
|
use openssl::bn::{BigNum, MsbOption};
|
||||||
use openssl::ec::{EcGroup, EcKey};
|
use openssl::ec::{EcGroup, EcKey};
|
||||||
|
use openssl::error::ErrorStack;
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
use openssl::nid::Nid;
|
use openssl::nid::Nid;
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
use openssl::x509::{X509, X509Builder, X509Extension, X509NameBuilder};
|
use openssl::x509::extension::{
|
||||||
|
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName,
|
||||||
|
SubjectKeyIdentifier,
|
||||||
|
};
|
||||||
|
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};
|
||||||
|
use tokio_rustls::rustls::ServerConfig;
|
||||||
|
use tokio_rustls::rustls::crypto::CryptoProvider;
|
||||||
|
use tokio_rustls::rustls::pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer};
|
||||||
|
use tokio_rustls::rustls::server::ClientHello;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
use visit_rs::Visit;
|
||||||
|
|
||||||
use crate::SOURCE_DATE;
|
use crate::SOURCE_DATE;
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
|
use crate::db::model::Database;
|
||||||
|
use crate::db::{DbAccess, DbAccessMut};
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::Hostname;
|
||||||
use crate::init::check_time_is_synchronized;
|
use crate::init::check_time_is_synchronized;
|
||||||
|
use crate::net::gateway::GatewayInfo;
|
||||||
|
use crate::net::tls::TlsHandler;
|
||||||
|
use crate::net::web_server::{Accept, ExtractVisitor, TcpMetadata, extract};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
|
pub fn gen_nistp256() -> Result<PKey<Private>, ErrorStack> {
|
||||||
|
PKey::from_ec_key(EcKey::generate(&*EcGroup::from_curve_name(
|
||||||
|
Nid::X9_62_PRIME256V1,
|
||||||
|
)?)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
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")]
|
||||||
@@ -61,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,
|
||||||
@@ -96,9 +107,7 @@ impl Model<CertStore> {
|
|||||||
} else {
|
} else {
|
||||||
PKeyPair {
|
PKeyPair {
|
||||||
ed25519: PKey::generate_ed25519()?,
|
ed25519: PKey::generate_ed25519()?,
|
||||||
nistp256: PKey::from_ec_key(EcKey::generate(&*EcGroup::from_curve_name(
|
nistp256: gen_nistp256()?,
|
||||||
Nid::X9_62_PRIME256V1,
|
|
||||||
)?)?)?,
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let int_key = self.as_int_key().de()?.0;
|
let int_key = self.as_int_key().de()?.0;
|
||||||
@@ -125,6 +134,16 @@ impl Model<CertStore> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl DbAccess<CertStore> for Database {
|
||||||
|
fn access<'a>(db: &'a Model<Self>) -> &'a Model<CertStore> {
|
||||||
|
db.as_private().as_key_store().as_local_certs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DbAccessMut<CertStore> for Database {
|
||||||
|
fn access_mut<'a>(db: &'a mut Model<Self>) -> &'a mut Model<CertStore> {
|
||||||
|
db.as_private_mut().as_key_store_mut().as_local_certs_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct CertData {
|
pub struct CertData {
|
||||||
@@ -221,12 +240,16 @@ impl CertPair {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn root_ca_start_time() -> Result<SystemTime, Error> {
|
pub async fn root_ca_start_time() -> SystemTime {
|
||||||
Ok(if check_time_is_synchronized().await? {
|
if check_time_is_synchronized()
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
} else {
|
} else {
|
||||||
*SOURCE_DATE
|
*SOURCE_DATE
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EC_CURVE_NAME: nid::Nid = nid::Nid::X9_62_PRIME256V1;
|
const EC_CURVE_NAME: nid::Nid = nid::Nid::X9_62_PRIME256V1;
|
||||||
@@ -300,22 +323,16 @@ pub fn make_root_cert(
|
|||||||
let cfg = conf::Conf::new(conf::ConfMethod::default())?;
|
let cfg = conf::Conf::new(conf::ConfMethod::default())?;
|
||||||
let ctx = builder.x509v3_context(None, Some(&cfg));
|
let ctx = builder.x509v3_context(None, Some(&cfg));
|
||||||
// subjectKeyIdentifier = hash
|
// subjectKeyIdentifier = hash
|
||||||
let subject_key_identifier =
|
let subject_key_identifier = SubjectKeyIdentifier::new().build(&ctx)?;
|
||||||
X509Extension::new_nid(Some(&cfg), Some(&ctx), Nid::SUBJECT_KEY_IDENTIFIER, "hash")?;
|
|
||||||
// basicConstraints = critical, CA:true, pathlen:0
|
// basicConstraints = critical, CA:true, pathlen:0
|
||||||
let basic_constraints = X509Extension::new_nid(
|
let basic_constraints = BasicConstraints::new().critical().ca().build()?;
|
||||||
Some(&cfg),
|
|
||||||
Some(&ctx),
|
|
||||||
Nid::BASIC_CONSTRAINTS,
|
|
||||||
"critical,CA:true",
|
|
||||||
)?;
|
|
||||||
// keyUsage = critical, digitalSignature, cRLSign, keyCertSign
|
// keyUsage = critical, digitalSignature, cRLSign, keyCertSign
|
||||||
let key_usage = X509Extension::new_nid(
|
let key_usage = KeyUsage::new()
|
||||||
Some(&cfg),
|
.critical()
|
||||||
Some(&ctx),
|
.digital_signature()
|
||||||
Nid::KEY_USAGE,
|
.crl_sign()
|
||||||
"critical,digitalSignature,cRLSign,keyCertSign",
|
.key_cert_sign()
|
||||||
)?;
|
.build()?;
|
||||||
builder.append_extension(subject_key_identifier)?;
|
builder.append_extension(subject_key_identifier)?;
|
||||||
builder.append_extension(basic_constraints)?;
|
builder.append_extension(basic_constraints)?;
|
||||||
builder.append_extension(key_usage)?;
|
builder.append_extension(key_usage)?;
|
||||||
@@ -350,30 +367,23 @@ pub fn make_int_cert(
|
|||||||
|
|
||||||
let cfg = conf::Conf::new(conf::ConfMethod::default())?;
|
let cfg = conf::Conf::new(conf::ConfMethod::default())?;
|
||||||
let ctx = builder.x509v3_context(Some(&signer.1), Some(&cfg));
|
let ctx = builder.x509v3_context(Some(&signer.1), Some(&cfg));
|
||||||
|
|
||||||
// subjectKeyIdentifier = hash
|
// subjectKeyIdentifier = hash
|
||||||
let subject_key_identifier =
|
let subject_key_identifier = SubjectKeyIdentifier::new().build(&ctx)?;
|
||||||
X509Extension::new_nid(Some(&cfg), Some(&ctx), Nid::SUBJECT_KEY_IDENTIFIER, "hash")?;
|
|
||||||
// authorityKeyIdentifier = keyid:always,issuer
|
// authorityKeyIdentifier = keyid:always,issuer
|
||||||
let authority_key_identifier = X509Extension::new_nid(
|
let authority_key_identifier = AuthorityKeyIdentifier::new()
|
||||||
Some(&cfg),
|
.keyid(false)
|
||||||
Some(&ctx),
|
.issuer(true)
|
||||||
Nid::AUTHORITY_KEY_IDENTIFIER,
|
.build(&ctx)?;
|
||||||
"keyid:always,issuer",
|
|
||||||
)?;
|
|
||||||
// basicConstraints = critical, CA:true, pathlen:0
|
// basicConstraints = critical, CA:true, pathlen:0
|
||||||
let basic_constraints = X509Extension::new_nid(
|
let basic_constraints = BasicConstraints::new().critical().ca().pathlen(0).build()?;
|
||||||
Some(&cfg),
|
|
||||||
Some(&ctx),
|
|
||||||
Nid::BASIC_CONSTRAINTS,
|
|
||||||
"critical,CA:true,pathlen:0",
|
|
||||||
)?;
|
|
||||||
// keyUsage = critical, digitalSignature, cRLSign, keyCertSign
|
// keyUsage = critical, digitalSignature, cRLSign, keyCertSign
|
||||||
let key_usage = X509Extension::new_nid(
|
let key_usage = KeyUsage::new()
|
||||||
Some(&cfg),
|
.critical()
|
||||||
Some(&ctx),
|
.digital_signature()
|
||||||
Nid::KEY_USAGE,
|
.crl_sign()
|
||||||
"critical,digitalSignature,cRLSign,keyCertSign",
|
.key_cert_sign()
|
||||||
)?;
|
.build()?;
|
||||||
builder.append_extension(subject_key_identifier)?;
|
builder.append_extension(subject_key_identifier)?;
|
||||||
builder.append_extension(authority_key_identifier)?;
|
builder.append_extension(authority_key_identifier)?;
|
||||||
builder.append_extension(basic_constraints)?;
|
builder.append_extension(basic_constraints)?;
|
||||||
@@ -423,6 +433,16 @@ impl SANInfo {
|
|||||||
}
|
}
|
||||||
Self { dns, ips }
|
Self { dns, ips }
|
||||||
}
|
}
|
||||||
|
pub fn x509_extension(&self) -> SubjectAlternativeName {
|
||||||
|
let mut san = SubjectAlternativeName::new();
|
||||||
|
for h in &self.dns {
|
||||||
|
san.dns(&h.as_str());
|
||||||
|
}
|
||||||
|
for ip in &self.ips {
|
||||||
|
san.ip(&ip.to_string());
|
||||||
|
}
|
||||||
|
san
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl std::fmt::Display for SANInfo {
|
impl std::fmt::Display for SANInfo {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
@@ -485,28 +505,20 @@ pub fn make_leaf_cert(
|
|||||||
// Extensions
|
// Extensions
|
||||||
let cfg = conf::Conf::new(conf::ConfMethod::default())?;
|
let cfg = conf::Conf::new(conf::ConfMethod::default())?;
|
||||||
let ctx = builder.x509v3_context(Some(&signer.1), Some(&cfg));
|
let ctx = builder.x509v3_context(Some(&signer.1), Some(&cfg));
|
||||||
// subjectKeyIdentifier = hash
|
|
||||||
let subject_key_identifier =
|
|
||||||
X509Extension::new_nid(Some(&cfg), Some(&ctx), Nid::SUBJECT_KEY_IDENTIFIER, "hash")?;
|
|
||||||
// authorityKeyIdentifier = keyid:always,issuer
|
|
||||||
let authority_key_identifier = X509Extension::new_nid(
|
|
||||||
Some(&cfg),
|
|
||||||
Some(&ctx),
|
|
||||||
Nid::AUTHORITY_KEY_IDENTIFIER,
|
|
||||||
"keyid,issuer:always",
|
|
||||||
)?;
|
|
||||||
let basic_constraints =
|
|
||||||
X509Extension::new_nid(Some(&cfg), Some(&ctx), Nid::BASIC_CONSTRAINTS, "CA:FALSE")?;
|
|
||||||
let key_usage = X509Extension::new_nid(
|
|
||||||
Some(&cfg),
|
|
||||||
Some(&ctx),
|
|
||||||
Nid::KEY_USAGE,
|
|
||||||
"critical,digitalSignature,keyEncipherment",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let san_string = applicant.1.to_string();
|
let subject_key_identifier = SubjectKeyIdentifier::new().build(&ctx)?;
|
||||||
let subject_alt_name =
|
let authority_key_identifier = AuthorityKeyIdentifier::new()
|
||||||
X509Extension::new_nid(Some(&cfg), Some(&ctx), Nid::SUBJECT_ALT_NAME, &san_string)?;
|
.keyid(false)
|
||||||
|
.issuer(true)
|
||||||
|
.build(&ctx)?;
|
||||||
|
let subject_alt_name = applicant.1.x509_extension().build(&ctx)?;
|
||||||
|
let basic_constraints = BasicConstraints::new().build()?;
|
||||||
|
let key_usage = KeyUsage::new()
|
||||||
|
.critical()
|
||||||
|
.digital_signature()
|
||||||
|
.key_encipherment()
|
||||||
|
.build()?;
|
||||||
|
|
||||||
builder.append_extension(subject_key_identifier)?;
|
builder.append_extension(subject_key_identifier)?;
|
||||||
builder.append_extension(authority_key_identifier)?;
|
builder.append_extension(authority_key_identifier)?;
|
||||||
builder.append_extension(subject_alt_name)?;
|
builder.append_extension(subject_alt_name)?;
|
||||||
@@ -518,3 +530,161 @@ pub fn make_leaf_cert(
|
|||||||
let cert = builder.build();
|
let cert = builder.build();
|
||||||
Ok(cert)
|
Ok(cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub fn make_self_signed(applicant: (&PKey<Private>, &SANInfo)) -> Result<X509, Error> {
|
||||||
|
let mut builder = X509Builder::new()?;
|
||||||
|
builder.set_version(CERTIFICATE_VERSION)?;
|
||||||
|
|
||||||
|
let embargo = Asn1Time::from_unix(unix_time(SystemTime::now()) - 86400)?;
|
||||||
|
builder.set_not_before(&embargo)?;
|
||||||
|
|
||||||
|
// Google Apple and Mozilla reject certificate horizons longer than 398 days
|
||||||
|
// https://techbeacon.com/security/google-apple-mozilla-enforce-1-year-max-security-certifications
|
||||||
|
let expiration = Asn1Time::days_from_now(397)?;
|
||||||
|
builder.set_not_after(&expiration)?;
|
||||||
|
|
||||||
|
builder.set_serial_number(&*rand_serial()?)?;
|
||||||
|
|
||||||
|
let mut subject_name_builder = X509NameBuilder::new()?;
|
||||||
|
subject_name_builder.append_entry_by_text(
|
||||||
|
"CN",
|
||||||
|
applicant
|
||||||
|
.1
|
||||||
|
.dns
|
||||||
|
.first()
|
||||||
|
.map(MaybeWildcard::as_str)
|
||||||
|
.unwrap_or("localhost"),
|
||||||
|
)?;
|
||||||
|
subject_name_builder.append_entry_by_text("O", "Start9")?;
|
||||||
|
subject_name_builder.append_entry_by_text("OU", "StartOS")?;
|
||||||
|
let subject_name = subject_name_builder.build();
|
||||||
|
builder.set_subject_name(&subject_name)?;
|
||||||
|
|
||||||
|
builder.set_issuer_name(&subject_name)?;
|
||||||
|
|
||||||
|
builder.set_pubkey(&applicant.0)?;
|
||||||
|
|
||||||
|
// Extensions
|
||||||
|
let cfg = conf::Conf::new(conf::ConfMethod::default())?;
|
||||||
|
let ctx = builder.x509v3_context(None, Some(&cfg));
|
||||||
|
|
||||||
|
let subject_key_identifier = SubjectKeyIdentifier::new().build(&ctx)?;
|
||||||
|
let authority_key_identifier = AuthorityKeyIdentifier::new()
|
||||||
|
.keyid(false)
|
||||||
|
.issuer(true)
|
||||||
|
.build(&ctx)?;
|
||||||
|
let subject_alt_name = applicant.1.x509_extension().build(&ctx)?;
|
||||||
|
let basic_constraints = BasicConstraints::new().build()?;
|
||||||
|
let key_usage = KeyUsage::new()
|
||||||
|
.critical()
|
||||||
|
.digital_signature()
|
||||||
|
.key_encipherment()
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
builder.append_extension(subject_key_identifier)?;
|
||||||
|
builder.append_extension(authority_key_identifier)?;
|
||||||
|
builder.append_extension(subject_alt_name)?;
|
||||||
|
builder.append_extension(basic_constraints)?;
|
||||||
|
builder.append_extension(key_usage)?;
|
||||||
|
|
||||||
|
builder.sign(&applicant.0, MessageDigest::sha256())?;
|
||||||
|
|
||||||
|
let cert = builder.build();
|
||||||
|
Ok(cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RootCaTlsHandler<M: HasModel> {
|
||||||
|
pub db: TypedPatchDb<M>,
|
||||||
|
pub crypto_provider: Arc<CryptoProvider>,
|
||||||
|
}
|
||||||
|
impl<M: HasModel> Clone for RootCaTlsHandler<M> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
db: self.db.clone(),
|
||||||
|
crypto_provider: self.crypto_provider.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A, M> TlsHandler<'a, A> for RootCaTlsHandler<M>
|
||||||
|
where
|
||||||
|
A: Accept + 'a,
|
||||||
|
<A as Accept>::Metadata: Visit<ExtractVisitor<TcpMetadata>>
|
||||||
|
+ Visit<ExtractVisitor<GatewayInfo>>
|
||||||
|
+ Clone
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
|
M: HasModel<Model = Model<M>> + DbAccessMut<CertStore> + Send + Sync,
|
||||||
|
{
|
||||||
|
async fn get_config(
|
||||||
|
&mut self,
|
||||||
|
hello: &ClientHello<'_>,
|
||||||
|
metadata: &<A as Accept>::Metadata,
|
||||||
|
) -> Option<ServerConfig> {
|
||||||
|
let hostnames: BTreeSet<InternedString> = hello
|
||||||
|
.server_name()
|
||||||
|
.map(InternedString::from)
|
||||||
|
.into_iter()
|
||||||
|
.chain(
|
||||||
|
extract::<TcpMetadata, _>(metadata)
|
||||||
|
.map(|m| m.local_addr.ip())
|
||||||
|
.as_ref()
|
||||||
|
.map(InternedString::from_display),
|
||||||
|
)
|
||||||
|
.chain(
|
||||||
|
extract::<GatewayInfo, _>(metadata)
|
||||||
|
.and_then(|i| i.info.ip_info)
|
||||||
|
.and_then(|i| i.wan_ip)
|
||||||
|
.as_ref()
|
||||||
|
.map(InternedString::from_display),
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
let cert = self
|
||||||
|
.db
|
||||||
|
.mutate(|db| M::access_mut(db).cert_for(&hostnames))
|
||||||
|
.await
|
||||||
|
.result
|
||||||
|
.log_err()?;
|
||||||
|
let cfg = ServerConfig::builder_with_provider(self.crypto_provider.clone())
|
||||||
|
.with_safe_default_protocol_versions()
|
||||||
|
.log_err()?
|
||||||
|
.with_no_client_auth();
|
||||||
|
if hello
|
||||||
|
.signature_schemes()
|
||||||
|
.contains(&tokio_rustls::rustls::SignatureScheme::ED25519)
|
||||||
|
{
|
||||||
|
cfg.with_single_cert(
|
||||||
|
cert.fullchain_ed25519()
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| {
|
||||||
|
Ok(tokio_rustls::rustls::pki_types::CertificateDer::from(
|
||||||
|
c.to_der()?,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Result<_, Error>>()
|
||||||
|
.log_err()?,
|
||||||
|
PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
|
||||||
|
cert.leaf.keys.ed25519.private_key_to_pkcs8().log_err()?,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
cfg.with_single_cert(
|
||||||
|
cert.fullchain_nistp256()
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| {
|
||||||
|
Ok(tokio_rustls::rustls::pki_types::CertificateDer::from(
|
||||||
|
c.to_der()?,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Result<_, Error>>()
|
||||||
|
.log_err()?,
|
||||||
|
PrivateKeyDer::from(PrivatePkcs8KeyDer::from(
|
||||||
|
cert.leaf.keys.nistp256.private_key_to_pkcs8().log_err()?,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.log_err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use async_compression::tokio::bufread::GzipEncoder;
|
|||||||
use axum::Router;
|
use axum::Router;
|
||||||
use axum::body::Body;
|
use axum::body::Body;
|
||||||
use axum::extract::{self as x, Request};
|
use axum::extract::{self as x, Request};
|
||||||
use axum::response::{Redirect, Response};
|
use axum::response::{IntoResponse, Redirect, Response};
|
||||||
use axum::routing::{any, get};
|
use axum::routing::{any, get};
|
||||||
use base64::display::Base64Display;
|
use base64::display::Base64Display;
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
@@ -26,16 +26,19 @@ use models::PackageId;
|
|||||||
use new_mime_guess::MimeGuess;
|
use new_mime_guess::MimeGuess;
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
use rpc_toolkit::{Context, HttpServer, Server};
|
use rpc_toolkit::{Context, HttpServer, ParentHandler, Server};
|
||||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt, BufReader};
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt, BufReader};
|
||||||
use tokio_util::io::ReaderStream;
|
use tokio_util::io::ReaderStream;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::Hostname;
|
||||||
|
use crate::main_api;
|
||||||
use crate::middleware::auth::{Auth, HasValidSession};
|
use crate::middleware::auth::{Auth, HasValidSession};
|
||||||
use crate::middleware::cors::Cors;
|
use crate::middleware::cors::Cors;
|
||||||
use crate::middleware::db::SyncDb;
|
use crate::middleware::db::SyncDb;
|
||||||
|
use crate::net::gateway::GatewayInfo;
|
||||||
|
use crate::net::tls::TlsHandshakeInfo;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::rpc_continuations::{Guid, RpcContinuations};
|
use crate::rpc_continuations::{Guid, RpcContinuations};
|
||||||
use crate::s9pk::S9pk;
|
use crate::s9pk::S9pk;
|
||||||
@@ -46,7 +49,6 @@ use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
|||||||
use crate::util::io::open_file;
|
use crate::util::io::open_file;
|
||||||
use crate::util::net::SyncBody;
|
use crate::util::net::SyncBody;
|
||||||
use crate::util::serde::BASE64;
|
use crate::util::serde::BASE64;
|
||||||
use crate::{diagnostic_api, init_api, install_api, main_api, setup_api};
|
|
||||||
|
|
||||||
const NOT_FOUND: &[u8] = b"Not Found";
|
const NOT_FOUND: &[u8] = b"Not Found";
|
||||||
const METHOD_NOT_ALLOWED: &[u8] = b"Method Not Allowed";
|
const METHOD_NOT_ALLOWED: &[u8] = b"Method Not Allowed";
|
||||||
@@ -55,26 +57,151 @@ const INTERNAL_SERVER_ERROR: &[u8] = b"Internal Server Error";
|
|||||||
|
|
||||||
const PROXY_STRIP_HEADERS: &[&str] = &["cookie", "host", "origin", "referer", "user-agent"];
|
const PROXY_STRIP_HEADERS: &[&str] = &["cookie", "host", "origin", "referer", "user-agent"];
|
||||||
|
|
||||||
#[cfg(all(feature = "startd", not(feature = "test")))]
|
pub const EMPTY_DIR: Dir<'_> = Dir::new("", &[]);
|
||||||
const EMBEDDED_UIS: Dir<'_> =
|
|
||||||
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static");
|
|
||||||
#[cfg(not(all(feature = "startd", not(feature = "test"))))]
|
|
||||||
const EMBEDDED_UIS: Dir<'_> = Dir::new("", &[]);
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[macro_export]
|
||||||
pub enum UiMode {
|
macro_rules! else_empty_dir {
|
||||||
Setup,
|
($cfg:meta => $dir:expr) => {{
|
||||||
Install,
|
#[cfg(all($cfg, not(feature = "test")))]
|
||||||
Main,
|
{
|
||||||
|
$dir
|
||||||
|
}
|
||||||
|
#[cfg(not(all($cfg, not(feature = "test"))))]
|
||||||
|
{
|
||||||
|
crate::net::static_server::EMPTY_DIR
|
||||||
|
}
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiMode {
|
const EMBEDDED_UI_ROOT: Dir<'_> = else_empty_dir!(
|
||||||
fn path(&self, path: &str) -> PathBuf {
|
feature = "startd" =>
|
||||||
match self {
|
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static")
|
||||||
Self::Setup => Path::new("setup-wizard").join(path),
|
);
|
||||||
Self::Install => Path::new("install-wizard").join(path),
|
|
||||||
Self::Main => Path::new("ui").join(path),
|
pub trait UiContext: Context + AsRef<RpcContinuations> + Clone + Sized {
|
||||||
|
const UI_DIR: &'static Dir<'static>;
|
||||||
|
fn api() -> ParentHandler<Self>;
|
||||||
|
fn middleware(server: Server<Self>) -> HttpServer<Self>;
|
||||||
|
fn extend_router(self, router: Router) -> Router {
|
||||||
|
router
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiContext for RpcContext {
|
||||||
|
const UI_DIR: &'static Dir<'static> = &else_empty_dir!(
|
||||||
|
feature = "startd" =>
|
||||||
|
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static/ui")
|
||||||
|
);
|
||||||
|
fn api() -> ParentHandler<Self> {
|
||||||
|
main_api()
|
||||||
|
}
|
||||||
|
fn middleware(server: Server<Self>) -> HttpServer<Self> {
|
||||||
|
server
|
||||||
|
.middleware(Cors::new())
|
||||||
|
.middleware(Auth::new())
|
||||||
|
.middleware(SyncDb::new())
|
||||||
|
}
|
||||||
|
fn extend_router(self, router: Router) -> Router {
|
||||||
|
async fn https_redirect_if_public_http(
|
||||||
|
req: Request,
|
||||||
|
next: axum::middleware::Next,
|
||||||
|
) -> Response {
|
||||||
|
if req
|
||||||
|
.extensions()
|
||||||
|
.get::<GatewayInfo>()
|
||||||
|
.map_or(false, |p| p.info.public())
|
||||||
|
&& req.extensions().get::<TlsHandshakeInfo>().is_none()
|
||||||
|
{
|
||||||
|
Redirect::temporary(&format!(
|
||||||
|
"https://{}{}",
|
||||||
|
req.headers()
|
||||||
|
.get(HOST)
|
||||||
|
.and_then(|s| s.to_str().ok())
|
||||||
|
.unwrap_or("localhost"),
|
||||||
|
req.uri()
|
||||||
|
))
|
||||||
|
.into_response()
|
||||||
|
} else {
|
||||||
|
next.run(req).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/proxy/{url}", {
|
||||||
|
let ctx = self.clone();
|
||||||
|
any(move |x::Path(url): x::Path<String>, request: Request| {
|
||||||
|
let ctx = ctx.clone();
|
||||||
|
async move {
|
||||||
|
proxy_request(ctx, request, url)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(server_error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.nest("/s9pk", s9pk_router(self.clone()))
|
||||||
|
.route(
|
||||||
|
"/static/local-root-ca.crt",
|
||||||
|
get(move || {
|
||||||
|
let ctx = self.clone();
|
||||||
|
async move {
|
||||||
|
ctx.account
|
||||||
|
.peek(|account| cert_send(&account.root_ca_cert, &account.hostname))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.layer(axum::middleware::from_fn(https_redirect_if_public_http))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiContext for InitContext {
|
||||||
|
const UI_DIR: &'static Dir<'static> = &else_empty_dir!(
|
||||||
|
feature = "startd" =>
|
||||||
|
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static/ui")
|
||||||
|
);
|
||||||
|
fn api() -> ParentHandler<Self> {
|
||||||
|
main_api()
|
||||||
|
}
|
||||||
|
fn middleware(server: Server<Self>) -> HttpServer<Self> {
|
||||||
|
server.middleware(Cors::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiContext for DiagnosticContext {
|
||||||
|
const UI_DIR: &'static Dir<'static> = &else_empty_dir!(
|
||||||
|
feature = "startd" =>
|
||||||
|
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static/ui")
|
||||||
|
);
|
||||||
|
fn api() -> ParentHandler<Self> {
|
||||||
|
main_api()
|
||||||
|
}
|
||||||
|
fn middleware(server: Server<Self>) -> HttpServer<Self> {
|
||||||
|
server.middleware(Cors::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiContext for SetupContext {
|
||||||
|
const UI_DIR: &'static Dir<'static> = &else_empty_dir!(
|
||||||
|
feature = "startd" =>
|
||||||
|
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static/setup-wizard")
|
||||||
|
);
|
||||||
|
fn api() -> ParentHandler<Self> {
|
||||||
|
main_api()
|
||||||
|
}
|
||||||
|
fn middleware(server: Server<Self>) -> HttpServer<Self> {
|
||||||
|
server.middleware(Cors::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiContext for InstallContext {
|
||||||
|
const UI_DIR: &'static Dir<'static> = &else_empty_dir!(
|
||||||
|
feature = "startd" =>
|
||||||
|
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static/install-wizard")
|
||||||
|
);
|
||||||
|
fn api() -> ParentHandler<Self> {
|
||||||
|
main_api()
|
||||||
|
}
|
||||||
|
fn middleware(server: Server<Self>) -> HttpServer<Self> {
|
||||||
|
server.middleware(Cors::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,24 +238,23 @@ pub fn rpc_router<C: Context + Clone + AsRef<RpcContinuations>>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve_ui(req: Request, ui_mode: UiMode) -> Result<Response, Error> {
|
fn serve_ui<C: UiContext>(req: Request) -> Result<Response, Error> {
|
||||||
let (request_parts, _body) = req.into_parts();
|
let (request_parts, _body) = req.into_parts();
|
||||||
match &request_parts.method {
|
match &request_parts.method {
|
||||||
&Method::GET | &Method::HEAD => {
|
&Method::GET | &Method::HEAD => {
|
||||||
let uri_path = ui_mode.path(
|
let uri_path = request_parts
|
||||||
request_parts
|
.uri
|
||||||
.uri
|
.path()
|
||||||
.path()
|
.strip_prefix('/')
|
||||||
.strip_prefix('/')
|
.unwrap_or(request_parts.uri.path());
|
||||||
.unwrap_or(request_parts.uri.path()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let file = EMBEDDED_UIS
|
let file = C::UI_DIR
|
||||||
.get_file(&*uri_path)
|
.get_file(uri_path)
|
||||||
.or_else(|| EMBEDDED_UIS.get_file(&*ui_mode.path("index.html")));
|
.or_else(|| C::UI_DIR.get_file("index.html"));
|
||||||
|
|
||||||
if let Some(file) = file {
|
if let Some(file) = file {
|
||||||
FileData::from_embedded(&request_parts, file)?.into_response(&request_parts)
|
FileData::from_embedded(&request_parts, file, C::UI_DIR)?
|
||||||
|
.into_response(&request_parts)
|
||||||
} else {
|
} else {
|
||||||
Ok(not_found())
|
Ok(not_found())
|
||||||
}
|
}
|
||||||
@@ -137,79 +263,15 @@ fn serve_ui(req: Request, ui_mode: UiMode) -> Result<Response, Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_ui_router(ctx: SetupContext) -> Router {
|
pub fn ui_router<C: UiContext>(ctx: C) -> Router {
|
||||||
rpc_router(
|
ctx.clone()
|
||||||
ctx.clone(),
|
.extend_router(rpc_router(
|
||||||
Server::new(move || ready(Ok(ctx.clone())), setup_api()).middleware(Cors::new()),
|
ctx.clone(),
|
||||||
)
|
C::middleware(Server::new(move || ready(Ok(ctx.clone())), C::api())),
|
||||||
.fallback(any(|request: Request| async move {
|
))
|
||||||
serve_ui(request, UiMode::Setup).unwrap_or_else(server_error)
|
.fallback(any(|request: Request| async move {
|
||||||
}))
|
serve_ui::<C>(request).unwrap_or_else(server_error)
|
||||||
}
|
}))
|
||||||
|
|
||||||
pub fn diagnostic_ui_router(ctx: DiagnosticContext) -> Router {
|
|
||||||
rpc_router(
|
|
||||||
ctx.clone(),
|
|
||||||
Server::new(move || ready(Ok(ctx.clone())), diagnostic_api()).middleware(Cors::new()),
|
|
||||||
)
|
|
||||||
.fallback(any(|request: Request| async move {
|
|
||||||
serve_ui(request, UiMode::Main).unwrap_or_else(server_error)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn install_ui_router(ctx: InstallContext) -> Router {
|
|
||||||
rpc_router(
|
|
||||||
ctx.clone(),
|
|
||||||
Server::new(move || ready(Ok(ctx.clone())), install_api()).middleware(Cors::new()),
|
|
||||||
)
|
|
||||||
.fallback(any(|request: Request| async move {
|
|
||||||
serve_ui(request, UiMode::Install).unwrap_or_else(server_error)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_ui_router(ctx: InitContext) -> Router {
|
|
||||||
rpc_router(
|
|
||||||
ctx.clone(),
|
|
||||||
Server::new(move || ready(Ok(ctx.clone())), init_api()).middleware(Cors::new()),
|
|
||||||
)
|
|
||||||
.fallback(any(|request: Request| async move {
|
|
||||||
serve_ui(request, UiMode::Main).unwrap_or_else(server_error)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main_ui_router(ctx: RpcContext) -> Router {
|
|
||||||
rpc_router(ctx.clone(), {
|
|
||||||
let ctx = ctx.clone();
|
|
||||||
Server::new(move || ready(Ok(ctx.clone())), main_api::<RpcContext>())
|
|
||||||
.middleware(Cors::new())
|
|
||||||
.middleware(Auth::new())
|
|
||||||
.middleware(SyncDb::new())
|
|
||||||
})
|
|
||||||
.route("/proxy/{url}", {
|
|
||||||
let ctx = ctx.clone();
|
|
||||||
any(move |x::Path(url): x::Path<String>, request: Request| {
|
|
||||||
let ctx = ctx.clone();
|
|
||||||
async move {
|
|
||||||
proxy_request(ctx, request, url)
|
|
||||||
.await
|
|
||||||
.unwrap_or_else(server_error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.nest("/s9pk", s9pk_router(ctx.clone()))
|
|
||||||
.route(
|
|
||||||
"/static/local-root-ca.crt",
|
|
||||||
get(move || {
|
|
||||||
let ctx = ctx.clone();
|
|
||||||
async move {
|
|
||||||
let account = ctx.account.read().await;
|
|
||||||
cert_send(&account.root_ca_cert, &account.hostname)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.fallback(any(|request: Request| async move {
|
|
||||||
serve_ui(request, UiMode::Main).unwrap_or_else(server_error)
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresher() -> Router {
|
pub fn refresher() -> Router {
|
||||||
@@ -229,20 +291,6 @@ pub fn refresher() -> Router {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redirecter() -> Router {
|
|
||||||
Router::new().fallback(get(|request: Request| async move {
|
|
||||||
Redirect::temporary(&format!(
|
|
||||||
"https://{}{}",
|
|
||||||
request
|
|
||||||
.headers()
|
|
||||||
.get(HOST)
|
|
||||||
.and_then(|s| s.to_str().ok())
|
|
||||||
.unwrap_or("localhost"),
|
|
||||||
request.uri()
|
|
||||||
))
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn proxy_request(ctx: RpcContext, request: Request, url: String) -> Result<Response, Error> {
|
async fn proxy_request(ctx: RpcContext, request: Request, url: String) -> Result<Response, Error> {
|
||||||
if_authorized(&ctx, request, |mut request| async {
|
if_authorized(&ctx, request, |mut request| async {
|
||||||
for header in PROXY_STRIP_HEADERS {
|
for header in PROXY_STRIP_HEADERS {
|
||||||
@@ -492,6 +540,7 @@ impl FileData {
|
|||||||
fn from_embedded(
|
fn from_embedded(
|
||||||
req: &RequestParts,
|
req: &RequestParts,
|
||||||
file: &'static include_dir::File<'static>,
|
file: &'static include_dir::File<'static>,
|
||||||
|
ui_dir: &'static Dir<'static>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let path = file.path();
|
let path = file.path();
|
||||||
let (encoding, data, len, content_range) = if let Some(range) = req.headers.get(RANGE) {
|
let (encoding, data, len, content_range) = if let Some(range) = req.headers.get(RANGE) {
|
||||||
@@ -533,12 +582,12 @@ impl FileData {
|
|||||||
.fold((None, file.contents()), |acc, e| {
|
.fold((None, file.contents()), |acc, e| {
|
||||||
if let Some(file) = (e == "br")
|
if let Some(file) = (e == "br")
|
||||||
.then_some(())
|
.then_some(())
|
||||||
.and_then(|_| EMBEDDED_UIS.get_file(format!("{}.br", path.display())))
|
.and_then(|_| ui_dir.get_file(format!("{}.br", path.display())))
|
||||||
{
|
{
|
||||||
(Some("br"), file.contents())
|
(Some("br"), file.contents())
|
||||||
} else if let Some(file) = (e == "gzip" && acc.0 != Some("br"))
|
} else if let Some(file) = (e == "gzip" && acc.0 != Some("br"))
|
||||||
.then_some(())
|
.then_some(())
|
||||||
.and_then(|_| EMBEDDED_UIS.get_file(format!("{}.gz", path.display())))
|
.and_then(|_| ui_dir.get_file(format!("{}.gz", path.display())))
|
||||||
{
|
{
|
||||||
(Some("gzip"), file.contents())
|
(Some("gzip"), file.contents())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
315
core/startos/src/net/tls.rs
Normal file
315
core/startos/src/net/tls.rs
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use std::task::{Poll, ready};
|
||||||
|
|
||||||
|
use futures::future::BoxFuture;
|
||||||
|
use futures::stream::FuturesUnordered;
|
||||||
|
use futures::{FutureExt, StreamExt};
|
||||||
|
use imbl_value::InternedString;
|
||||||
|
use openssl::x509::X509Ref;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio_rustls::LazyConfigAcceptor;
|
||||||
|
use tokio_rustls::rustls::crypto::CryptoProvider;
|
||||||
|
use tokio_rustls::rustls::pki_types::CertificateDer;
|
||||||
|
use tokio_rustls::rustls::server::{Acceptor, ClientHello, ResolvesServerCert};
|
||||||
|
use tokio_rustls::rustls::sign::CertifiedKey;
|
||||||
|
use tokio_rustls::rustls::{ClientConfig, RootCertStore, ServerConfig};
|
||||||
|
use visit_rs::{Visit, VisitFields};
|
||||||
|
|
||||||
|
use crate::net::web_server::{Accept, AcceptStream, MetadataVisitor};
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::util::io::{BackTrackingIO, ReadWriter};
|
||||||
|
use crate::util::serde::MaybeUtf8String;
|
||||||
|
use crate::util::sync::SyncMutex;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, VisitFields)]
|
||||||
|
pub struct TlsMetadata<M> {
|
||||||
|
pub inner: M,
|
||||||
|
pub tls_info: TlsHandshakeInfo,
|
||||||
|
}
|
||||||
|
impl<V: MetadataVisitor<Result = ()>, M: Visit<V>> Visit<V> for TlsMetadata<M> {
|
||||||
|
fn visit(&self, visitor: &mut V) -> <V as visit_rs::Visitor>::Result {
|
||||||
|
self.visit_fields(visitor).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TlsHandshakeInfo {
|
||||||
|
pub sni: Option<InternedString>,
|
||||||
|
pub alpn: Vec<MaybeUtf8String>,
|
||||||
|
}
|
||||||
|
impl<V: MetadataVisitor> Visit<V> for TlsHandshakeInfo {
|
||||||
|
fn visit(&self, visitor: &mut V) -> <V as visit_rs::Visitor>::Result {
|
||||||
|
visitor.visit(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TlsHandler<'a, A: Accept> {
|
||||||
|
fn get_config(
|
||||||
|
&'a mut self,
|
||||||
|
hello: &'a ClientHello<'a>,
|
||||||
|
metadata: &'a A::Metadata,
|
||||||
|
) -> impl Future<Output = Option<ServerConfig>> + Send + 'a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ChainedHandler<H0, H1>(pub H0, pub H1);
|
||||||
|
impl<'a, A, H0, H1> TlsHandler<'a, A> for ChainedHandler<H0, H1>
|
||||||
|
where
|
||||||
|
A: Accept + 'a,
|
||||||
|
<A as Accept>::Metadata: Send + Sync,
|
||||||
|
H0: TlsHandler<'a, A> + Send,
|
||||||
|
H1: TlsHandler<'a, A> + Send,
|
||||||
|
{
|
||||||
|
async fn get_config(
|
||||||
|
&'a mut self,
|
||||||
|
hello: &'a ClientHello<'a>,
|
||||||
|
metadata: &'a <A as Accept>::Metadata,
|
||||||
|
) -> Option<ServerConfig> {
|
||||||
|
if let Some(config) = self.0.get_config(hello, metadata).await {
|
||||||
|
return Some(config);
|
||||||
|
}
|
||||||
|
self.1.get_config(hello, metadata).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TlsHandlerWrapper<I, W> {
|
||||||
|
pub inner: I,
|
||||||
|
pub wrapper: W,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait WrapTlsHandler<A: Accept> {
|
||||||
|
fn wrap<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
prev: ServerConfig,
|
||||||
|
hello: &'a ClientHello<'a>,
|
||||||
|
metadata: &'a <A as Accept>::Metadata,
|
||||||
|
) -> impl Future<Output = Option<ServerConfig>> + Send + 'a
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A, I, W> TlsHandler<'a, A> for TlsHandlerWrapper<I, W>
|
||||||
|
where
|
||||||
|
A: Accept + 'a,
|
||||||
|
<A as Accept>::Metadata: Send + Sync,
|
||||||
|
I: TlsHandler<'a, A> + Send,
|
||||||
|
W: WrapTlsHandler<A> + Send,
|
||||||
|
{
|
||||||
|
async fn get_config(
|
||||||
|
&'a mut self,
|
||||||
|
hello: &'a ClientHello<'a>,
|
||||||
|
metadata: &'a <A as Accept>::Metadata,
|
||||||
|
) -> Option<ServerConfig> {
|
||||||
|
let prev = self.inner.get_config(hello, metadata).await?;
|
||||||
|
self.wrapper.wrap(prev, hello, metadata).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SingleCertResolver(pub Arc<CertifiedKey>);
|
||||||
|
impl ResolvesServerCert for SingleCertResolver {
|
||||||
|
fn resolve(&self, _: ClientHello) -> Option<Arc<CertifiedKey>> {
|
||||||
|
Some(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TlsListener<A: Accept, H: for<'a> TlsHandler<'a, A>> {
|
||||||
|
pub accept: A,
|
||||||
|
pub tls_handler: H,
|
||||||
|
in_progress: SyncMutex<
|
||||||
|
FuturesUnordered<
|
||||||
|
BoxFuture<
|
||||||
|
'static,
|
||||||
|
(
|
||||||
|
H,
|
||||||
|
Result<Option<(TlsMetadata<A::Metadata>, AcceptStream)>, Error>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
impl<A: Accept, H: for<'a> TlsHandler<'a, A>> TlsListener<A, H> {
|
||||||
|
pub fn new(accept: A, cert_handler: H) -> Self {
|
||||||
|
Self {
|
||||||
|
accept,
|
||||||
|
tls_handler: cert_handler,
|
||||||
|
in_progress: SyncMutex::new(FuturesUnordered::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<A, H> Accept for TlsListener<A, H>
|
||||||
|
where
|
||||||
|
A: Accept + 'static,
|
||||||
|
A::Metadata: Send + 'static,
|
||||||
|
for<'a> H: TlsHandler<'a, A> + Clone + Send + 'static,
|
||||||
|
{
|
||||||
|
type Metadata = TlsMetadata<A::Metadata>;
|
||||||
|
fn poll_accept(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
|
||||||
|
self.in_progress.mutate(|in_progress| {
|
||||||
|
loop {
|
||||||
|
if !in_progress.is_empty() {
|
||||||
|
if let Poll::Ready(Some((handler, res))) = in_progress.poll_next_unpin(cx) {
|
||||||
|
if let Some(res) = res.transpose() {
|
||||||
|
self.tls_handler = handler;
|
||||||
|
return Poll::Ready(res);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (metadata, stream) = ready!(self.accept.poll_accept(cx)?);
|
||||||
|
let mut tls_handler = self.tls_handler.clone();
|
||||||
|
let mut fut = async move {
|
||||||
|
let res = async {
|
||||||
|
let mut acceptor = LazyConfigAcceptor::new(
|
||||||
|
Acceptor::default(),
|
||||||
|
BackTrackingIO::new(stream),
|
||||||
|
);
|
||||||
|
let mut mid: tokio_rustls::StartHandshake<BackTrackingIO<AcceptStream>> =
|
||||||
|
match (&mut acceptor).await {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => {
|
||||||
|
let mut stream =
|
||||||
|
acceptor.take_io().or_not_found("acceptor io")?;
|
||||||
|
let (_, buf) = stream.rewind();
|
||||||
|
if std::str::from_utf8(buf)
|
||||||
|
.ok()
|
||||||
|
.and_then(|buf| {
|
||||||
|
buf.lines()
|
||||||
|
.map(|l| l.trim())
|
||||||
|
.filter(|l| !l.is_empty())
|
||||||
|
.next()
|
||||||
|
})
|
||||||
|
.map_or(false, |buf| {
|
||||||
|
regex::Regex::new("[A-Z]+ (.+) HTTP/1")
|
||||||
|
.unwrap()
|
||||||
|
.is_match(buf)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
handle_http_on_https(stream).await.log_err();
|
||||||
|
|
||||||
|
return Ok(None);
|
||||||
|
} else {
|
||||||
|
return Err(e).with_kind(ErrorKind::Network);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let hello = mid.client_hello();
|
||||||
|
if let Some(cfg) = tls_handler.get_config(&hello, &metadata).await {
|
||||||
|
let metadata = TlsMetadata {
|
||||||
|
inner: metadata,
|
||||||
|
tls_info: TlsHandshakeInfo {
|
||||||
|
sni: hello.server_name().map(InternedString::intern),
|
||||||
|
alpn: hello
|
||||||
|
.alpn()
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|a| MaybeUtf8String(a.to_vec()))
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let buffered = mid.io.stop_buffering();
|
||||||
|
mid.io
|
||||||
|
.write_all(&buffered)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
return Ok(Some((
|
||||||
|
metadata,
|
||||||
|
Box::pin(mid.into_stream(Arc::new(cfg)).await?) as AcceptStream,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
.await;
|
||||||
|
(tls_handler, res)
|
||||||
|
}
|
||||||
|
.boxed();
|
||||||
|
match fut.poll_unpin(cx) {
|
||||||
|
Poll::Pending => {
|
||||||
|
in_progress.push(fut);
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
Poll::Ready((handler, res)) => {
|
||||||
|
if let Some(res) = res.transpose() {
|
||||||
|
self.tls_handler = handler;
|
||||||
|
return Poll::Ready(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_http_on_https(stream: impl ReadWriter + Unpin + 'static) -> Result<(), Error> {
|
||||||
|
use axum::body::Body;
|
||||||
|
use axum::extract::Request;
|
||||||
|
use axum::response::Response;
|
||||||
|
use http::Uri;
|
||||||
|
|
||||||
|
use crate::net::static_server::server_error;
|
||||||
|
|
||||||
|
hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new())
|
||||||
|
.serve_connection(
|
||||||
|
hyper_util::rt::TokioIo::new(stream),
|
||||||
|
hyper_util::service::TowerToHyperService::new(axum::Router::new().fallback(
|
||||||
|
axum::routing::method_routing::any(move |req: Request| async move {
|
||||||
|
match async move {
|
||||||
|
let host = req
|
||||||
|
.headers()
|
||||||
|
.get(http::header::HOST)
|
||||||
|
.and_then(|host| host.to_str().ok());
|
||||||
|
if let Some(host) = host {
|
||||||
|
let uri = Uri::from_parts({
|
||||||
|
let mut parts = req.uri().to_owned().into_parts();
|
||||||
|
parts.scheme = Some("https".parse()?);
|
||||||
|
parts.authority = Some(host.parse()?);
|
||||||
|
parts
|
||||||
|
})?;
|
||||||
|
Response::builder()
|
||||||
|
.status(http::StatusCode::TEMPORARY_REDIRECT)
|
||||||
|
.header(http::header::LOCATION, uri.to_string())
|
||||||
|
.body(Body::default())
|
||||||
|
} else {
|
||||||
|
Response::builder()
|
||||||
|
.status(http::StatusCode::BAD_REQUEST)
|
||||||
|
.body(Body::from("Host header required"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Error redirecting http request on ssl port: {e}");
|
||||||
|
tracing::error!("{e:?}");
|
||||||
|
server_error(Error::new(e, ErrorKind::Network))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::new(color_eyre::eyre::Report::msg(e), ErrorKind::Network))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client_config<'a, I: IntoIterator<Item = &'a X509Ref>>(
|
||||||
|
crypto_provider: Arc<CryptoProvider>,
|
||||||
|
root_certs: I,
|
||||||
|
) -> Result<ClientConfig, Error> {
|
||||||
|
let mut certs = RootCertStore::empty();
|
||||||
|
for cert in root_certs {
|
||||||
|
certs
|
||||||
|
.add(CertificateDer::from_slice(&cert.to_der()?))
|
||||||
|
.with_kind(ErrorKind::OpenSsl)?;
|
||||||
|
}
|
||||||
|
Ok(ClientConfig::builder_with_provider(crypto_provider.clone())
|
||||||
|
.with_safe_default_protocol_versions()
|
||||||
|
.with_kind(ErrorKind::OpenSsl)?
|
||||||
|
.with_root_certificates(certs)
|
||||||
|
.with_no_client_auth())
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ use std::sync::{Arc, Weak};
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use arti_client::config::onion_service::OnionServiceConfigBuilder;
|
use arti_client::config::onion_service::OnionServiceConfigBuilder;
|
||||||
use arti_client::{DataStream, TorClient, TorClientConfig};
|
use arti_client::{TorClient, TorClientConfig};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
@@ -14,7 +14,7 @@ use futures::{FutureExt, StreamExt};
|
|||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler};
|
use rpc_toolkit::{Context, Empty, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
@@ -34,8 +34,8 @@ use crate::util::actor::background::BackgroundJobQueue;
|
|||||||
use crate::util::future::Until;
|
use crate::util::future::Until;
|
||||||
use crate::util::io::ReadWriter;
|
use crate::util::io::ReadWriter;
|
||||||
use crate::util::serde::{
|
use crate::util::serde::{
|
||||||
deserialize_from_str, display_serializable, serialize_display, Base64, HandlerExtSerde,
|
BASE64, Base64, HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable,
|
||||||
WithIoFormat, BASE64,
|
serialize_display,
|
||||||
};
|
};
|
||||||
use crate::util::sync::{SyncMutex, SyncRwLock, Watch};
|
use crate::util::sync::{SyncMutex, SyncRwLock, Watch};
|
||||||
|
|
||||||
@@ -628,11 +628,7 @@ impl TorController {
|
|||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
if rm {
|
if rm { s.remove(&addr) } else { None }
|
||||||
s.remove(&addr)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}) {
|
}) {
|
||||||
s.shutdown().await
|
s.shutdown().await
|
||||||
} else {
|
} else {
|
||||||
@@ -861,11 +857,11 @@ impl OnionService {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn proxy_all<Rcs: FromIterator<Arc<()>>>(
|
pub async fn proxy_all<Rcs: FromIterator<Arc<()>>>(
|
||||||
&self,
|
&self,
|
||||||
bindings: impl IntoIterator<Item = (u16, SocketAddr)>,
|
bindings: impl IntoIterator<Item = (u16, SocketAddr)>,
|
||||||
) -> Rcs {
|
) -> Result<Rcs, Error> {
|
||||||
self.0.bindings.mutate(|b| {
|
Ok(self.0.bindings.mutate(|b| {
|
||||||
bindings
|
bindings
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(port, target)| {
|
.map(|(port, target)| {
|
||||||
@@ -879,7 +875,7 @@ impl OnionService {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gc(&self) -> bool {
|
pub fn gc(&self) -> bool {
|
||||||
1046
core/startos/src/net/tor/ctor.rs
Normal file
1046
core/startos/src/net/tor/ctor.rs
Normal file
File diff suppressed because it is too large
Load Diff
10
core/startos/src/net/tor/mod.rs
Normal file
10
core/startos/src/net/tor/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#[cfg(feature = "arti")]
|
||||||
|
mod arti;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "arti"))]
|
||||||
|
mod ctor;
|
||||||
|
|
||||||
|
#[cfg(feature = "arti")]
|
||||||
|
pub use arti::{OnionAddress, OnionStore, TorController, TorSecretKey, tor_api};
|
||||||
|
#[cfg(not(feature = "arti"))]
|
||||||
|
pub use ctor::{OnionAddress, OnionStore, TorController, TorSecretKey, tor_api};
|
||||||
@@ -2,16 +2,17 @@ use clap::Parser;
|
|||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use models::GatewayId;
|
use models::GatewayId;
|
||||||
use patch_db::json_ptr::JsonPointer;
|
use patch_db::json_ptr::JsonPointer;
|
||||||
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
|
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType};
|
use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType};
|
||||||
|
use crate::net::host::all_hosts;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::io::{write_file_atomic, TmpDir};
|
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
|
use crate::util::io::{TmpDir, write_file_atomic};
|
||||||
|
|
||||||
pub fn tunnel_api<C: Context>() -> ParentHandler<C> {
|
pub fn tunnel_api<C: Context>() -> ParentHandler<C> {
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
@@ -125,14 +126,44 @@ pub async fn remove_tunnel(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
if existing.as_device_type().de()? != Some(NetworkInterfaceType::Wireguard) {
|
if existing.as_deref().as_device_type().de()? != Some(NetworkInterfaceType::Wireguard) {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("network interface {id} is not a proxy"),
|
eyre!("network interface {id} is not a proxy"),
|
||||||
ErrorKind::InvalidRequest,
|
ErrorKind::InvalidRequest,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
for host in all_hosts(db) {
|
||||||
|
let host = host?;
|
||||||
|
host.as_public_domains_mut()
|
||||||
|
.mutate(|p| Ok(p.retain(|_, v| v.gateway != id)))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
|
||||||
ctx.net_controller.net_iface.delete_iface(&id).await?;
|
ctx.net_controller.net_iface.delete_iface(&id).await?;
|
||||||
|
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
for host in all_hosts(db) {
|
||||||
|
let host = host?;
|
||||||
|
host.as_bindings_mut().mutate(|b| {
|
||||||
|
Ok(b.values_mut().for_each(|v| {
|
||||||
|
v.net.private_disabled.remove(&id);
|
||||||
|
v.net.public_enabled.remove(&id);
|
||||||
|
}))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6};
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -7,13 +8,56 @@ use futures::stream::BoxStream;
|
|||||||
use futures::{StreamExt, TryStreamExt};
|
use futures::{StreamExt, TryStreamExt};
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
|
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
|
||||||
|
use models::GatewayId;
|
||||||
use nix::net::if_::if_nametoindex;
|
use nix::net::if_::if_nametoindex;
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::db::model::public::{IpInfo, NetworkInterfaceType};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
|
|
||||||
|
pub async fn load_ip_info() -> Result<BTreeMap<GatewayId, IpInfo>, Error> {
|
||||||
|
let output = String::from_utf8(
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("-o")
|
||||||
|
.arg("addr")
|
||||||
|
.arg("show")
|
||||||
|
.invoke(crate::ErrorKind::Network)
|
||||||
|
.await?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let err_fn = || {
|
||||||
|
Error::new(
|
||||||
|
eyre!("malformed output from `ip`"),
|
||||||
|
crate::ErrorKind::Network,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut res = BTreeMap::<GatewayId, IpInfo>::new();
|
||||||
|
|
||||||
|
for line in output.lines() {
|
||||||
|
let split = line.split_ascii_whitespace().collect::<Vec<_>>();
|
||||||
|
let iface = GatewayId::from(InternedString::from(*split.get(1).ok_or_else(&err_fn)?));
|
||||||
|
let subnet: IpNet = split.get(3).ok_or_else(&err_fn)?.parse()?;
|
||||||
|
let ip_info = res.entry(iface.clone()).or_default();
|
||||||
|
ip_info.name = iface.into();
|
||||||
|
ip_info.scope_id = split
|
||||||
|
.get(0)
|
||||||
|
.ok_or_else(&err_fn)?
|
||||||
|
.strip_suffix(":")
|
||||||
|
.ok_or_else(&err_fn)?
|
||||||
|
.parse()?;
|
||||||
|
ip_info.subnets.insert(subnet);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, ip_info) in res.iter_mut() {
|
||||||
|
ip_info.device_type = probe_iface_type(id.as_str()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ipv6_is_link_local(addr: Ipv6Addr) -> bool {
|
pub fn ipv6_is_link_local(addr: Ipv6Addr) -> bool {
|
||||||
(addr.segments()[0] & 0xffc0) == 0xfe80
|
(addr.segments()[0] & 0xffc0) == 0xfe80
|
||||||
}
|
}
|
||||||
@@ -75,6 +119,22 @@ pub async fn get_iface_ipv6_addr(iface: &str) -> Result<Option<(Ipv6Addr, Ipv6Ne
|
|||||||
.transpose()?)
|
.transpose()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn probe_iface_type(iface: &str) -> Option<NetworkInterfaceType> {
|
||||||
|
match tokio::fs::read_to_string(Path::new("/sys/class/net").join(iface).join("uevent"))
|
||||||
|
.await
|
||||||
|
.ok()?
|
||||||
|
.lines()
|
||||||
|
.find_map(|l| l.strip_prefix("DEVTYPE="))
|
||||||
|
{
|
||||||
|
Some("wlan") => Some(NetworkInterfaceType::Wireless),
|
||||||
|
Some("bridge") => Some(NetworkInterfaceType::Bridge),
|
||||||
|
Some("wireguard") => Some(NetworkInterfaceType::Wireguard),
|
||||||
|
None if iface_is_physical(iface).await => Some(NetworkInterfaceType::Ethernet),
|
||||||
|
None if iface_is_loopback(iface).await => Some(NetworkInterfaceType::Loopback),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn iface_is_physical(iface: &str) -> bool {
|
pub async fn iface_is_physical(iface: &str) -> bool {
|
||||||
tokio::fs::metadata(Path::new("/sys/class/net").join(iface).join("device"))
|
tokio::fs::metadata(Path::new("/sys/class/net").join(iface).join("device"))
|
||||||
.await
|
.await
|
||||||
@@ -87,6 +147,19 @@ pub async fn iface_is_wireless(iface: &str) -> bool {
|
|||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn iface_is_bridge(iface: &str) -> bool {
|
||||||
|
tokio::fs::metadata(Path::new("/sys/class/net").join(iface).join("bridge"))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn iface_is_loopback(iface: &str) -> bool {
|
||||||
|
tokio::fs::read_to_string(Path::new("/sys/class/net").join(iface).join("type"))
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.map_or(false, |x| x.trim() == "772")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_interfaces() -> BoxStream<'static, Result<String, Error>> {
|
pub fn list_interfaces() -> BoxStream<'static, Result<String, Error>> {
|
||||||
try_stream! {
|
try_stream! {
|
||||||
let mut ifaces = tokio::fs::read_dir("/sys/class/net").await?;
|
let mut ifaces = tokio::fs::read_dir("/sys/class/net").await?;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,70 +1,201 @@
|
|||||||
|
use core::fmt;
|
||||||
|
use std::any::Any;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::task::Poll;
|
use std::task::{Poll, ready};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use futures::future::Either;
|
use futures::future::Either;
|
||||||
use futures::FutureExt;
|
use futures::{FutureExt, TryFutureExt};
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
|
use http::Extensions;
|
||||||
use hyper_util::rt::{TokioIo, TokioTimer};
|
use hyper_util::rt::{TokioIo, TokioTimer};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::TcpListener;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
use visit_rs::{Visit, VisitFields, Visitor};
|
||||||
|
|
||||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
use crate::net::static_server::{UiContext, ui_router};
|
||||||
use crate::net::gateway::{
|
|
||||||
lookup_info_by_addr, NetworkInterfaceListener, SelfContainedNetworkInterfaceListener,
|
|
||||||
};
|
|
||||||
use crate::net::static_server::{
|
|
||||||
diagnostic_ui_router, init_ui_router, install_ui_router, main_ui_router, redirecter, refresher,
|
|
||||||
setup_ui_router,
|
|
||||||
};
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::actor::background::BackgroundJobQueue;
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
|
use crate::util::io::ReadWriter;
|
||||||
use crate::util::sync::{SyncRwLock, Watch};
|
use crate::util::sync::{SyncRwLock, Watch};
|
||||||
|
|
||||||
pub struct Accepted {
|
pub type AcceptStream = Pin<Box<dyn ReadWriter + Send + 'static>>;
|
||||||
pub https_redirect: bool,
|
|
||||||
pub stream: TcpStream,
|
pub trait MetadataVisitor: Visitor<Result = ()> {
|
||||||
|
fn visit<M: Clone + Send + Sync + 'static>(&mut self, metadata: &M) -> Self::Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExtensionVisitor<'a>(&'a mut Extensions);
|
||||||
|
impl<'a> Visitor for ExtensionVisitor<'a> {
|
||||||
|
type Result = ();
|
||||||
|
}
|
||||||
|
impl<'a> MetadataVisitor for ExtensionVisitor<'a> {
|
||||||
|
fn visit<M: Clone + Send + Sync + 'static>(&mut self, metadata: &M) -> Self::Result {
|
||||||
|
self.0.insert(metadata.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Visit<ExtensionVisitor<'a>>
|
||||||
|
for Box<dyn for<'x> Visit<ExtensionVisitor<'x>> + Send + Sync + 'static>
|
||||||
|
{
|
||||||
|
fn visit(
|
||||||
|
&self,
|
||||||
|
visitor: &mut ExtensionVisitor<'a>,
|
||||||
|
) -> <ExtensionVisitor<'a> as Visitor>::Result {
|
||||||
|
(&**self).visit(visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExtractVisitor<T>(Option<T>);
|
||||||
|
impl<T> Visitor for ExtractVisitor<T> {
|
||||||
|
type Result = ();
|
||||||
|
}
|
||||||
|
impl<T: Clone + Send + Sync + 'static> MetadataVisitor for ExtractVisitor<T> {
|
||||||
|
fn visit<M: Clone + Send + Sync + 'static>(&mut self, metadata: &M) -> Self::Result {
|
||||||
|
if let Some(matching) = (metadata as &dyn Any).downcast_ref::<T>() {
|
||||||
|
self.0 = Some(matching.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn extract<
|
||||||
|
T: Clone + Send + Sync + 'static,
|
||||||
|
M: Visit<ExtractVisitor<T>> + Clone + Send + Sync + 'static,
|
||||||
|
>(
|
||||||
|
metadata: &M,
|
||||||
|
) -> Option<T> {
|
||||||
|
let mut visitor = ExtractVisitor(None);
|
||||||
|
metadata.visit(&mut visitor);
|
||||||
|
visitor.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct TcpMetadata {
|
||||||
|
pub peer_addr: SocketAddr,
|
||||||
|
pub local_addr: SocketAddr,
|
||||||
|
}
|
||||||
|
impl<V: MetadataVisitor> Visit<V> for TcpMetadata {
|
||||||
|
fn visit(&self, visitor: &mut V) -> <V as visit_rs::Visitor>::Result {
|
||||||
|
visitor.visit(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Accept {
|
pub trait Accept {
|
||||||
fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>>;
|
type Metadata: fmt::Debug;
|
||||||
|
fn poll_accept(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>>;
|
||||||
|
fn into_dyn(self) -> DynAccept
|
||||||
|
where
|
||||||
|
Self: Sized + Send + Sync + 'static,
|
||||||
|
for<'a> Self::Metadata: Visit<ExtensionVisitor<'a>> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
DynAccept::new(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Accept for Vec<TcpListener> {
|
impl Accept for TcpListener {
|
||||||
fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>> {
|
type Metadata = TcpMetadata;
|
||||||
for listener in &*self {
|
fn poll_accept(
|
||||||
if let Poll::Ready((stream, _)) = listener.poll_accept(cx)? {
|
&mut self,
|
||||||
return Poll::Ready(Ok(Accepted {
|
cx: &mut std::task::Context<'_>,
|
||||||
https_redirect: false,
|
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
|
||||||
stream,
|
if let Poll::Ready((stream, peer_addr)) = TcpListener::poll_accept(self, cx)? {
|
||||||
}));
|
if let Err(e) = socket2::SockRef::from(&stream).set_tcp_keepalive(
|
||||||
|
&socket2::TcpKeepalive::new()
|
||||||
|
.with_time(Duration::from_secs(900))
|
||||||
|
.with_interval(Duration::from_secs(60))
|
||||||
|
.with_retries(5),
|
||||||
|
) {
|
||||||
|
tracing::error!("Failed to set tcp keepalive: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
}
|
||||||
|
return Poll::Ready(Ok((
|
||||||
|
TcpMetadata {
|
||||||
|
local_addr: self.local_addr()?,
|
||||||
|
peer_addr,
|
||||||
|
},
|
||||||
|
Box::pin(stream),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> Accept for Vec<A>
|
||||||
|
where
|
||||||
|
A: Accept,
|
||||||
|
{
|
||||||
|
type Metadata = A::Metadata;
|
||||||
|
fn poll_accept(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
|
||||||
|
for listener in self {
|
||||||
|
if let Poll::Ready(accepted) = listener.poll_accept(cx)? {
|
||||||
|
return Poll::Ready(Ok(accepted));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Accept for NetworkInterfaceListener {
|
|
||||||
fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>> {
|
#[derive(Debug, Clone, VisitFields)]
|
||||||
NetworkInterfaceListener::poll_accept(self, cx, &true).map(|res| {
|
pub struct MapListenerMetadata<K, M> {
|
||||||
res.map(|a| {
|
pub inner: M,
|
||||||
let public = self
|
pub key: K,
|
||||||
.ip_info
|
}
|
||||||
.peek(|i| lookup_info_by_addr(i, a.bind).map_or(true, |(_, i)| i.public()));
|
impl<K, M, V> Visit<V> for MapListenerMetadata<K, M>
|
||||||
Accepted {
|
where
|
||||||
https_redirect: public,
|
V: MetadataVisitor,
|
||||||
stream: a.stream,
|
K: Visit<V>,
|
||||||
}
|
M: Visit<V>,
|
||||||
})
|
{
|
||||||
})
|
fn visit(&self, visitor: &mut V) -> <V as Visitor>::Result {
|
||||||
|
self.visit_fields(visitor).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Accept, B: Accept> Accept for Either<A, B> {
|
impl<K, A> Accept for BTreeMap<K, A>
|
||||||
fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>> {
|
where
|
||||||
|
K: Clone + fmt::Debug,
|
||||||
|
A: Accept,
|
||||||
|
{
|
||||||
|
type Metadata = MapListenerMetadata<K, A::Metadata>;
|
||||||
|
fn poll_accept(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
|
||||||
|
for (key, listener) in self {
|
||||||
|
if let Poll::Ready((metadata, stream)) = listener.poll_accept(cx)? {
|
||||||
|
return Poll::Ready(Ok((
|
||||||
|
MapListenerMetadata {
|
||||||
|
inner: metadata,
|
||||||
|
key: key.clone(),
|
||||||
|
},
|
||||||
|
stream,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, B> Accept for Either<A, B>
|
||||||
|
where
|
||||||
|
A: Accept,
|
||||||
|
B: Accept<Metadata = A::Metadata>,
|
||||||
|
{
|
||||||
|
type Metadata = A::Metadata;
|
||||||
|
fn poll_accept(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
|
||||||
match self {
|
match self {
|
||||||
Either::Left(a) => a.poll_accept(cx),
|
Either::Left(a) => a.poll_accept(cx),
|
||||||
Either::Right(b) => b.poll_accept(cx),
|
Either::Right(b) => b.poll_accept(cx),
|
||||||
@@ -72,7 +203,11 @@ impl<A: Accept, B: Accept> Accept for Either<A, B> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<A: Accept> Accept for Option<A> {
|
impl<A: Accept> Accept for Option<A> {
|
||||||
fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>> {
|
type Metadata = A::Metadata;
|
||||||
|
fn poll_accept(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
|
||||||
match self {
|
match self {
|
||||||
None => Poll::Pending,
|
None => Poll::Pending,
|
||||||
Some(a) => a.poll_accept(cx),
|
Some(a) => a.poll_accept(cx),
|
||||||
@@ -80,6 +215,66 @@ impl<A: Accept> Accept for Option<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait DynAcceptT: Send + Sync {
|
||||||
|
fn poll_accept(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Result<(DynMetadata, AcceptStream), Error>>;
|
||||||
|
}
|
||||||
|
impl<A> DynAcceptT for A
|
||||||
|
where
|
||||||
|
A: Accept + Send + Sync,
|
||||||
|
<A as Accept>::Metadata: DynMetadataT + 'static,
|
||||||
|
{
|
||||||
|
fn poll_accept(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Result<(DynMetadata, AcceptStream), Error>> {
|
||||||
|
let (metadata, stream) = ready!(Accept::poll_accept(self, cx)?);
|
||||||
|
Poll::Ready(Ok((DynMetadata(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;
|
||||||
|
fn poll_accept(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Result<(Self::Metadata, AcceptStream), Error>> {
|
||||||
|
DynAcceptT::poll_accept(&mut *self.0, cx)
|
||||||
|
}
|
||||||
|
fn into_dyn(self) -> DynAccept
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
for<'a> Self::Metadata: Visit<ExtensionVisitor<'a>> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DynAccept {
|
||||||
|
pub fn new<A>(accept: A) -> Self
|
||||||
|
where
|
||||||
|
A: Accept + Send + Sync + 'static,
|
||||||
|
for<'a> <A as Accept>::Metadata: Visit<ExtensionVisitor<'a>> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
Self(Box::new(accept))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[pin_project::pin_project]
|
#[pin_project::pin_project]
|
||||||
pub struct Acceptor<A: Accept> {
|
pub struct Acceptor<A: Accept> {
|
||||||
acceptor: Watch<A>,
|
acceptor: Watch<A>,
|
||||||
@@ -95,12 +290,15 @@ impl<A: Accept + Send + Sync + 'static> Acceptor<A> {
|
|||||||
self.acceptor.poll_changed(cx)
|
self.acceptor.poll_changed(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_accept(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<Accepted, Error>> {
|
fn poll_accept(
|
||||||
let _ = self.poll_changed(cx);
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Result<(A::Metadata, AcceptStream), Error>> {
|
||||||
|
while self.poll_changed(cx).is_ready() {}
|
||||||
self.acceptor.peek_mut(|a| a.poll_accept(cx))
|
self.acceptor.peek_mut(|a| a.poll_accept(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn accept(&mut self) -> Result<Accepted, Error> {
|
async fn accept(&mut self) -> Result<(A::Metadata, AcceptStream), Error> {
|
||||||
std::future::poll_fn(|cx| self.poll_accept(cx)).await
|
std::future::poll_fn(|cx| self.poll_accept(cx)).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,20 +309,73 @@ impl Acceptor<Vec<TcpListener>> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Acceptor<Vec<DynAccept>> {
|
||||||
pub type UpgradableListener =
|
pub async fn bind_dyn(listen: impl IntoIterator<Item = SocketAddr>) -> Result<Self, Error> {
|
||||||
Option<Either<SelfContainedNetworkInterfaceListener, NetworkInterfaceListener>>;
|
Ok(Self::new(
|
||||||
|
futures::future::try_join_all(
|
||||||
impl Acceptor<UpgradableListener> {
|
listen
|
||||||
pub fn bind_upgradable(listener: SelfContainedNetworkInterfaceListener) -> Self {
|
.into_iter()
|
||||||
Self::new(Some(Either::Left(listener)))
|
.map(TcpListener::bind)
|
||||||
|
.map(|f| f.map_ok(DynAccept::new)),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<K> Acceptor<BTreeMap<K, TcpListener>>
|
||||||
|
where
|
||||||
|
K: Ord + Clone + fmt::Debug + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
pub async fn bind_map(
|
||||||
|
listen: impl IntoIterator<Item = (K, SocketAddr)>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
Ok(Self::new(
|
||||||
|
futures::future::try_join_all(listen.into_iter().map(|(key, addr)| async move {
|
||||||
|
Ok::<_, Error>((
|
||||||
|
key,
|
||||||
|
TcpListener::bind(addr)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?,
|
||||||
|
))
|
||||||
|
}))
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<K> Acceptor<BTreeMap<K, DynAccept>>
|
||||||
|
where
|
||||||
|
K: Ord + Clone + fmt::Debug + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
pub async fn bind_map_dyn(
|
||||||
|
listen: impl IntoIterator<Item = (K, SocketAddr)>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
Ok(Self::new(
|
||||||
|
futures::future::try_join_all(listen.into_iter().map(|(key, addr)| async move {
|
||||||
|
Ok::<_, Error>((
|
||||||
|
key,
|
||||||
|
TcpListener::bind(addr)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?,
|
||||||
|
))
|
||||||
|
}))
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, listener)| (key, listener.into_dyn()))
|
||||||
|
.collect(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WebServerAcceptorSetter<A: Accept> {
|
pub struct WebServerAcceptorSetter<A: Accept> {
|
||||||
acceptor: Watch<A>,
|
acceptor: Watch<A>,
|
||||||
}
|
}
|
||||||
impl<A: Accept, B: Accept> WebServerAcceptorSetter<Option<Either<A, B>>> {
|
impl<A, B> WebServerAcceptorSetter<Option<Either<A, B>>>
|
||||||
|
where
|
||||||
|
A: Accept,
|
||||||
|
B: Accept<Metadata = A::Metadata>,
|
||||||
|
{
|
||||||
pub fn try_upgrade<F: FnOnce(A) -> Result<B, Error>>(&self, f: F) -> Result<(), Error> {
|
pub fn try_upgrade<F: FnOnce(A) -> Result<B, Error>>(&self, f: F) -> Result<(), Error> {
|
||||||
let mut res = Ok(());
|
let mut res = Ok(());
|
||||||
self.acceptor.send_modify(|a| {
|
self.acceptor.send_modify(|a| {
|
||||||
@@ -151,20 +402,24 @@ impl<A: Accept> Deref for WebServerAcceptorSetter<A> {
|
|||||||
|
|
||||||
pub struct WebServer<A: Accept> {
|
pub struct WebServer<A: Accept> {
|
||||||
shutdown: oneshot::Sender<()>,
|
shutdown: oneshot::Sender<()>,
|
||||||
router: Watch<Option<Router>>,
|
router: Watch<Router>,
|
||||||
acceptor: Watch<A>,
|
acceptor: Watch<A>,
|
||||||
thread: NonDetachingJoinHandle<()>,
|
thread: NonDetachingJoinHandle<()>,
|
||||||
}
|
}
|
||||||
impl<A: Accept + Send + Sync + 'static> WebServer<A> {
|
impl<A> WebServer<A>
|
||||||
|
where
|
||||||
|
A: Accept + Send + Sync + 'static,
|
||||||
|
for<'a> A::Metadata: Visit<ExtensionVisitor<'a>> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
pub fn acceptor_setter(&self) -> WebServerAcceptorSetter<A> {
|
pub fn acceptor_setter(&self) -> WebServerAcceptorSetter<A> {
|
||||||
WebServerAcceptorSetter {
|
WebServerAcceptorSetter {
|
||||||
acceptor: self.acceptor.clone(),
|
acceptor: self.acceptor.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(mut acceptor: Acceptor<A>) -> Self {
|
pub fn new(mut acceptor: Acceptor<A>, router: Router) -> Self {
|
||||||
let acceptor_send = acceptor.acceptor.clone();
|
let acceptor_send = acceptor.acceptor.clone();
|
||||||
let router = Watch::<Option<Router>>::new(None);
|
let router = Watch::new(router);
|
||||||
let service = router.clone_unseen();
|
let service = router.clone_unseen();
|
||||||
let (shutdown, shutdown_recv) = oneshot::channel();
|
let (shutdown, shutdown_recv) = oneshot::channel();
|
||||||
let thread = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
let thread = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
||||||
@@ -187,8 +442,14 @@ impl<A: Accept + Send + Sync + 'static> WebServer<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SwappableRouter(Watch<Option<Router>>, bool);
|
struct SwappableRouter<M> {
|
||||||
impl hyper::service::Service<hyper::Request<hyper::body::Incoming>> for SwappableRouter {
|
router: Watch<Router>,
|
||||||
|
metadata: M,
|
||||||
|
}
|
||||||
|
impl<M: for<'a> Visit<ExtensionVisitor<'a>> + Send + Sync + 'static>
|
||||||
|
hyper::service::Service<hyper::Request<hyper::body::Incoming>>
|
||||||
|
for SwappableRouter<M>
|
||||||
|
{
|
||||||
type Response = <Router as tower_service::Service<
|
type Response = <Router as tower_service::Service<
|
||||||
hyper::Request<hyper::body::Incoming>,
|
hyper::Request<hyper::body::Incoming>,
|
||||||
>>::Response;
|
>>::Response;
|
||||||
@@ -199,19 +460,13 @@ impl<A: Accept + Send + Sync + 'static> WebServer<A> {
|
|||||||
hyper::Request<hyper::body::Incoming>,
|
hyper::Request<hyper::body::Incoming>,
|
||||||
>>::Future;
|
>>::Future;
|
||||||
|
|
||||||
fn call(&self, req: hyper::Request<hyper::body::Incoming>) -> Self::Future {
|
fn call(&self, mut req: hyper::Request<hyper::body::Incoming>) -> Self::Future {
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
|
|
||||||
if self.1 {
|
self.metadata
|
||||||
redirecter().call(req)
|
.visit(&mut ExtensionVisitor(req.extensions_mut()));
|
||||||
} else {
|
|
||||||
let router = self.0.read();
|
self.router.read().call(req)
|
||||||
if let Some(mut router) = router {
|
|
||||||
router.call(req)
|
|
||||||
} else {
|
|
||||||
refresher().call(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,16 +493,16 @@ impl<A: Accept + Send + Sync + 'static> WebServer<A> {
|
|||||||
let mut err = None;
|
let mut err = None;
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
if let Err(e) = async {
|
if let Err(e) = async {
|
||||||
let accepted = acceptor.accept().await?;
|
let (metadata, stream) = acceptor.accept().await?;
|
||||||
queue.add_job(
|
queue.add_job(
|
||||||
graceful.watch(
|
graceful.watch(
|
||||||
server
|
server
|
||||||
.serve_connection_with_upgrades(
|
.serve_connection_with_upgrades(
|
||||||
TokioIo::new(accepted.stream),
|
TokioIo::new(stream),
|
||||||
SwappableRouter(
|
SwappableRouter {
|
||||||
service.clone(),
|
router: service.clone(),
|
||||||
accepted.https_redirect,
|
metadata,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
.into_owned(),
|
.into_owned(),
|
||||||
),
|
),
|
||||||
@@ -300,26 +555,10 @@ impl<A: Accept + Send + Sync + 'static> WebServer<A> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn serve_router(&mut self, router: Router) {
|
pub fn serve_router(&mut self, router: Router) {
|
||||||
self.router.send(Some(router))
|
self.router.send(router)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serve_main(&mut self, ctx: RpcContext) {
|
pub fn serve_ui_for<C: UiContext>(&mut self, ctx: C) {
|
||||||
self.serve_router(main_ui_router(ctx))
|
self.serve_router(ui_router(ctx))
|
||||||
}
|
|
||||||
|
|
||||||
pub fn serve_setup(&mut self, ctx: SetupContext) {
|
|
||||||
self.serve_router(setup_ui_router(ctx))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn serve_diagnostic(&mut self, ctx: DiagnosticContext) {
|
|
||||||
self.serve_router(diagnostic_ui_router(ctx))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn serve_install(&mut self, ctx: InstallContext) {
|
|
||||||
self.serve_router(install_ui_router(ctx))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn serve_init(&mut self, ctx: InitContext) {
|
|
||||||
self.serve_router(init_ui_router(ctx))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1017,6 +1017,31 @@ pub async fn synchronize_network_manager<P: AsRef<Path>>(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("rule")
|
||||||
|
.arg("add")
|
||||||
|
.arg("pref")
|
||||||
|
.arg("1000")
|
||||||
|
.arg("from")
|
||||||
|
.arg("all")
|
||||||
|
.arg("lookup")
|
||||||
|
.arg("main")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
Command::new("ip")
|
||||||
|
.arg("rule")
|
||||||
|
.arg("add")
|
||||||
|
.arg("pref")
|
||||||
|
.arg("1100")
|
||||||
|
.arg("from")
|
||||||
|
.arg("all")
|
||||||
|
.arg("lookup")
|
||||||
|
.arg("default")
|
||||||
|
.invoke(ErrorKind::Network)
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
|
||||||
Command::new("systemctl")
|
Command::new("systemctl")
|
||||||
.arg("restart")
|
.arg("restart")
|
||||||
.arg("NetworkManager")
|
.arg("NetworkManager")
|
||||||
|
|||||||
@@ -155,6 +155,16 @@ pub async fn remove(
|
|||||||
for id in ids {
|
for id in ids {
|
||||||
n.remove(&id)?;
|
n.remove(&id)?;
|
||||||
}
|
}
|
||||||
|
let mut unread = 0;
|
||||||
|
for (_, n) in n.as_entries()? {
|
||||||
|
if !n.as_seen().de()? {
|
||||||
|
unread += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.as_public_mut()
|
||||||
|
.as_server_info_mut()
|
||||||
|
.as_unread_notification_count_mut()
|
||||||
|
.ser(&unread)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@@ -179,6 +189,16 @@ pub async fn remove_before(
|
|||||||
for id in n.keys()?.range(..before) {
|
for id in n.keys()?.range(..before) {
|
||||||
n.remove(&id)?;
|
n.remove(&id)?;
|
||||||
}
|
}
|
||||||
|
let mut unread = 0;
|
||||||
|
for (_, n) in n.as_entries()? {
|
||||||
|
if !n.as_seen().de()? {
|
||||||
|
unread += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.as_public_mut()
|
||||||
|
.as_server_info_mut()
|
||||||
|
.as_unread_notification_count_mut()
|
||||||
|
.ser(&unread)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@@ -443,7 +463,8 @@ pub fn notify<T: NotificationType>(
|
|||||||
data,
|
data,
|
||||||
seen: false,
|
seen: false,
|
||||||
},
|
},
|
||||||
)
|
)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -356,11 +356,15 @@ 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"),
|
||||||
"aarch64" => install.arg("--target=arm64-efi"),
|
"aarch64" => install.arg("--target=arm64-efi"),
|
||||||
|
"riscv64" => install.arg("--target=riscv64-efi"),
|
||||||
_ => &mut install,
|
_ => &mut install,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -371,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?;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use http::HeaderMap;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use patch_db::PatchDb;
|
use patch_db::PatchDb;
|
||||||
use reqwest::{Client, Proxy};
|
use reqwest::{Client, Proxy};
|
||||||
@@ -168,25 +169,38 @@ impl CallRemote<RegistryContext> for CliContext {
|
|||||||
let url = if let Some(url) = self.registry_url.clone() {
|
let url = if let Some(url) = self.registry_url.clone() {
|
||||||
url
|
url
|
||||||
} else if self.registry_hostname.is_some() {
|
} else if self.registry_hostname.is_some() {
|
||||||
format!(
|
let mut url: Url = format!(
|
||||||
"http://{}",
|
"http://{}",
|
||||||
self.registry_listen.unwrap_or(DEFAULT_REGISTRY_LISTEN)
|
self.registry_listen.unwrap_or(DEFAULT_REGISTRY_LISTEN)
|
||||||
)
|
)
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(Error::from)?
|
.map_err(Error::from)?;
|
||||||
|
url.path_segments_mut()
|
||||||
|
.map_err(|_| Error::new(eyre!("cannot extend URL path"), ErrorKind::ParseUrl))?
|
||||||
|
.push("rpc")
|
||||||
|
.push("v0");
|
||||||
|
url
|
||||||
} else {
|
} else {
|
||||||
return Err(
|
return Err(
|
||||||
Error::new(eyre!("`--registry` required"), ErrorKind::InvalidRequest).into(),
|
Error::new(eyre!("`--registry` required"), ErrorKind::InvalidRequest).into(),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
method = method.strip_prefix("registry.").unwrap_or(method);
|
method = method.strip_prefix("registry.").unwrap_or(method);
|
||||||
let sig_context = self
|
let sig_context = self
|
||||||
.registry_hostname
|
.registry_hostname
|
||||||
.clone()
|
.clone()
|
||||||
.or(url.host().as_ref().map(InternedString::from_display))
|
.or_else(|| url.host().as_ref().map(InternedString::from_display));
|
||||||
.or_not_found("registry hostname")?;
|
|
||||||
|
|
||||||
crate::middleware::signature::call_remote(self, url, &sig_context, method, params).await
|
crate::middleware::signature::call_remote(
|
||||||
|
self,
|
||||||
|
url,
|
||||||
|
HeaderMap::new(),
|
||||||
|
sig_context.as_deref(),
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,61 +209,32 @@ impl CallRemote<RegistryContext, RegistryUrlParams> for RpcContext {
|
|||||||
&self,
|
&self,
|
||||||
mut method: &str,
|
mut method: &str,
|
||||||
params: Value,
|
params: Value,
|
||||||
RegistryUrlParams { registry }: RegistryUrlParams,
|
RegistryUrlParams { mut registry }: RegistryUrlParams,
|
||||||
) -> Result<Value, RpcError> {
|
) -> Result<Value, RpcError> {
|
||||||
use reqwest::Method;
|
let mut headers = HeaderMap::new();
|
||||||
use reqwest::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE};
|
headers.insert(
|
||||||
use rpc_toolkit::RpcResponse;
|
DEVICE_INFO_HEADER,
|
||||||
use rpc_toolkit::yajrc::{GenericRpcMethod, Id, RpcRequest};
|
DeviceInfo::load(self).await?.to_header_value(),
|
||||||
|
);
|
||||||
|
|
||||||
|
registry
|
||||||
|
.path_segments_mut()
|
||||||
|
.map_err(|_| Error::new(eyre!("cannot extend URL path"), ErrorKind::ParseUrl))?
|
||||||
|
.push("rpc")
|
||||||
|
.push("v0");
|
||||||
|
|
||||||
let url = registry.join("rpc/v0")?;
|
|
||||||
method = method.strip_prefix("registry.").unwrap_or(method);
|
method = method.strip_prefix("registry.").unwrap_or(method);
|
||||||
|
let sig_context = registry.host_str().map(InternedString::from);
|
||||||
|
|
||||||
let rpc_req = RpcRequest {
|
crate::middleware::signature::call_remote(
|
||||||
id: Some(Id::Number(0.into())),
|
self,
|
||||||
method: GenericRpcMethod::<_, _, Value>::new(method),
|
registry,
|
||||||
|
headers,
|
||||||
|
sig_context.as_deref(),
|
||||||
|
method,
|
||||||
params,
|
params,
|
||||||
};
|
)
|
||||||
let body = serde_json::to_vec(&rpc_req)?;
|
.await
|
||||||
let res = self
|
|
||||||
.client
|
|
||||||
.request(Method::POST, url)
|
|
||||||
.header(CONTENT_TYPE, "application/json")
|
|
||||||
.header(ACCEPT, "application/json")
|
|
||||||
.header(CONTENT_LENGTH, body.len())
|
|
||||||
.header(
|
|
||||||
DEVICE_INFO_HEADER,
|
|
||||||
DeviceInfo::load(self).await?.to_header_value(),
|
|
||||||
)
|
|
||||||
.body(body)
|
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !res.status().is_success() {
|
|
||||||
let status = res.status();
|
|
||||||
let txt = res.text().await?;
|
|
||||||
let mut res = Err(Error::new(
|
|
||||||
eyre!("{}", status.canonical_reason().unwrap_or(status.as_str())),
|
|
||||||
ErrorKind::Network,
|
|
||||||
));
|
|
||||||
if !txt.is_empty() {
|
|
||||||
res = res.with_ctx(|_| (ErrorKind::Network, txt));
|
|
||||||
}
|
|
||||||
return res.map_err(From::from);
|
|
||||||
}
|
|
||||||
|
|
||||||
match res
|
|
||||||
.headers()
|
|
||||||
.get(CONTENT_TYPE)
|
|
||||||
.and_then(|v| v.to_str().ok())
|
|
||||||
{
|
|
||||||
Some("application/json") => {
|
|
||||||
serde_json::from_slice::<RpcResponse>(&*res.bytes().await?)
|
|
||||||
.with_kind(ErrorKind::Deserialization)?
|
|
||||||
.result
|
|
||||||
}
|
|
||||||
_ => Err(Error::new(eyre!("unknown content type"), ErrorKind::Network).into()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ impl Middleware<RegistryContext> for DeviceInfoMiddleware {
|
|||||||
async move {
|
async move {
|
||||||
if metadata.get_device_info {
|
if metadata.get_device_info {
|
||||||
if let Some(device_info) = &self.device_info {
|
if let Some(device_info) = &self.device_info {
|
||||||
request.params["__device_info"] =
|
request.params["__DeviceInfo_device_info"] =
|
||||||
to_value(&DeviceInfo::from_header_value(device_info)?)?;
|
to_value(&DeviceInfo::from_header_value(device_info)?)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,9 +141,3 @@ pub fn registry_router(ctx: RegistryContext) -> Router {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Accept + Send + Sync + 'static> WebServer<A> {
|
|
||||||
pub fn serve_registry(&mut self, ctx: RegistryContext) {
|
|
||||||
self.serve_router(registry_router(ctx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ pub struct AddAssetParams {
|
|||||||
pub platform: InternedString,
|
pub platform: InternedString,
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub url: Url,
|
pub url: Url,
|
||||||
#[serde(rename = "__auth_signer")]
|
#[serde(rename = "__Auth_signer")]
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
pub signer: AnyVerifyingKey,
|
pub signer: AnyVerifyingKey,
|
||||||
pub signature: AnySignature,
|
pub signature: AnySignature,
|
||||||
@@ -289,7 +289,7 @@ pub struct RemoveAssetParams {
|
|||||||
pub version: Version,
|
pub version: Version,
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub platform: InternedString,
|
pub platform: InternedString,
|
||||||
#[serde(rename = "__auth_signer")]
|
#[serde(rename = "__Auth_signer")]
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
pub signer: AnyVerifyingKey,
|
pub signer: AnyVerifyingKey,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ pub struct SignAssetParams {
|
|||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
platform: InternedString,
|
platform: InternedString,
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_signer")]
|
#[serde(rename = "__Auth_signer")]
|
||||||
signer: AnyVerifyingKey,
|
signer: AnyVerifyingKey,
|
||||||
signature: AnySignature,
|
signature: AnySignature,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ pub struct AddVersionParams {
|
|||||||
pub source_version: VersionRange,
|
pub source_version: VersionRange,
|
||||||
#[arg(skip)]
|
#[arg(skip)]
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_signer")]
|
#[serde(rename = "__Auth_signer")]
|
||||||
pub signer: Option<AnyVerifyingKey>,
|
pub signer: Option<AnyVerifyingKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ pub struct GetOsVersionParams {
|
|||||||
platform: Option<InternedString>,
|
platform: Option<InternedString>,
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[arg(skip)]
|
#[arg(skip)]
|
||||||
#[serde(rename = "__device_info")]
|
#[serde(rename = "__DeviceInfo_device_info")]
|
||||||
pub device_info: Option<DeviceInfo>,
|
pub device_info: Option<DeviceInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ pub struct AddPackageParams {
|
|||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub url: Url,
|
pub url: Url,
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_signer")]
|
#[serde(rename = "__Auth_signer")]
|
||||||
pub uploader: AnyVerifyingKey,
|
pub uploader: AnyVerifyingKey,
|
||||||
pub commitment: MerkleArchiveCommitment,
|
pub commitment: MerkleArchiveCommitment,
|
||||||
pub signature: AnySignature,
|
pub signature: AnySignature,
|
||||||
@@ -169,7 +169,7 @@ pub struct RemovePackageParams {
|
|||||||
pub version: VersionString,
|
pub version: VersionString,
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[arg(skip)]
|
#[arg(skip)]
|
||||||
#[serde(rename = "__auth_signer")]
|
#[serde(rename = "__Auth_signer")]
|
||||||
pub signer: Option<AnyVerifyingKey>,
|
pub signer: Option<AnyVerifyingKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user