mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
Compare commits
9 Commits
v0.4.0-alp
...
v0.4.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
723dea100f | ||
|
|
c4419ed31f | ||
|
|
754ab86e51 | ||
|
|
04dab532cd | ||
|
|
add01ebc68 | ||
|
|
1cc9a1a30b | ||
|
|
92a1de7500 | ||
|
|
a6fedcff80 | ||
|
|
55eb999305 |
10
.github/workflows/startos-iso.yaml
vendored
10
.github/workflows/startos-iso.yaml
vendored
@@ -93,8 +93,18 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
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
|
- name: Make
|
||||||
run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar
|
run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: on
|
||||||
|
SCCACHE_GHA_VERSION: 0
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,8 +1,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.idea
|
.idea
|
||||||
system-images/binfmt/binfmt.tar
|
|
||||||
system-images/compat/compat.tar
|
|
||||||
system-images/util/util.tar
|
|
||||||
/*.img
|
/*.img
|
||||||
/*.img.gz
|
/*.img.gz
|
||||||
/*.img.xz
|
/*.img.xz
|
||||||
|
|||||||
92
Makefile
92
Makefile
@@ -1,3 +1,6 @@
|
|||||||
|
ls-files = $(shell git ls-files --cached --others --exclude-standard $1)
|
||||||
|
PROFILE = release
|
||||||
|
|
||||||
PLATFORM_FILE := $(shell ./check-platform.sh)
|
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)
|
||||||
@@ -9,23 +12,27 @@ IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo
|
|||||||
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 := $(shell git 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 := $(shell git ls-files debian/)
|
DEBIAN_SRC := $(call ls-files, debian/)
|
||||||
IMAGE_RECIPE_SRC := $(shell git 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)
|
||||||
COMPAT_SRC := $(shell git ls-files system-images/compat/)
|
CORE_SRC := $(call ls-files, core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE)
|
||||||
UTILS_SRC := $(shell git ls-files system-images/utils/)
|
WEB_SHARED_SRC := $(call ls-files, web/projects/shared) $(call ls-files, web/projects/marketplace) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js sdk/baseDist/package.json web/patchdb-ui-seed.json sdk/dist/package.json
|
||||||
BINFMT_SRC := $(shell git ls-files system-images/binfmt/)
|
WEB_UI_SRC := $(call ls-files, web/projects/ui)
|
||||||
CORE_SRC := $(shell git ls-files core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE)
|
WEB_SETUP_WIZARD_SRC := $(call ls-files, web/projects/setup-wizard)
|
||||||
WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell git ls-files web/projects/marketplace) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js sdk/baseDist/package.json web/patchdb-ui-seed.json sdk/dist/package.json
|
WEB_INSTALL_WIZARD_SRC := $(call ls-files, web/projects/install-wizard)
|
||||||
WEB_UI_SRC := $(shell git ls-files web/projects/ui)
|
|
||||||
WEB_SETUP_WIZARD_SRC := $(shell git ls-files web/projects/setup-wizard)
|
|
||||||
WEB_INSTALL_WIZARD_SRC := $(shell git ls-files web/projects/install-wizard)
|
|
||||||
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/release/startbox core/target/$(ARCH)-unknown-linux-musl/release/containerbox system-images/compat/docker-images/$(ARCH).tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar container-runtime/rootfs.$(ARCH).squashfs
|
COMPILED_TARGETS := core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox core/target/$(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 $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; fi) $(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then echo cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console; fi') $(PLATFORM_FILE)
|
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) \
|
||||||
|
$(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then \
|
||||||
|
echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; \
|
||||||
|
fi) \
|
||||||
|
$(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then \
|
||||||
|
echo cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console; \
|
||||||
|
echo cargo-deps/$(ARCH)-unknown-linux-musl/release/flamegraph; \
|
||||||
|
fi')
|
||||||
REBUILD_TYPES = 1
|
REBUILD_TYPES = 1
|
||||||
|
|
||||||
ifeq ($(REMOTE),)
|
ifeq ($(REMOTE),)
|
||||||
@@ -59,8 +66,6 @@ touch:
|
|||||||
metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f system-images/**/*.tar
|
|
||||||
rm -rf system-images/compat/target
|
|
||||||
rm -rf core/target
|
rm -rf core/target
|
||||||
rm -rf core/startos/bindings
|
rm -rf core/startos/bindings
|
||||||
rm -rf web/.angular
|
rm -rf web/.angular
|
||||||
@@ -95,17 +100,20 @@ test: | test-core test-sdk test-container-runtime
|
|||||||
test-core: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
test-core: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
./core/run-tests.sh
|
./core/run-tests.sh
|
||||||
|
|
||||||
test-sdk: $(shell git ls-files sdk) sdk/base/lib/osBindings/index.ts
|
test-sdk: $(call ls-files, sdk) sdk/base/lib/osBindings/index.ts
|
||||||
cd sdk && make test
|
cd sdk && make test
|
||||||
|
|
||||||
test-container-runtime: container-runtime/node_modules/.package-lock.json $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
test-container-runtime: container-runtime/node_modules/.package-lock.json $(call ls-files, container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
||||||
cd container-runtime && npm test
|
cd container-runtime && npm test
|
||||||
|
|
||||||
cli:
|
cli:
|
||||||
cd core && ./install-cli.sh
|
./core/install-cli.sh
|
||||||
|
|
||||||
registry:
|
registry:
|
||||||
cd core && ./build-registrybox.sh
|
./core/build-registrybox.sh
|
||||||
|
|
||||||
|
tunnel:
|
||||||
|
./core/build-tunnelbox.sh
|
||||||
|
|
||||||
deb: results/$(BASENAME).deb
|
deb: results/$(BASENAME).deb
|
||||||
|
|
||||||
@@ -126,12 +134,14 @@ results/$(BASENAME).$(IMAGE_TYPE) results/$(BASENAME).squashfs: $(IMAGE_RECIPE_S
|
|||||||
install: $(ALL_TARGETS)
|
install: $(ALL_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/release/startbox,$(DESTDIR)/usr/bin/startbox)
|
$(call cp,core/target/$(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)
|
||||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-sdk)
|
|
||||||
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 $(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); fi
|
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/$(ARCH)-unknown-linux-musl/release/flamegraph,$(DESTDIR)/usr/bin/flamegraph); \
|
||||||
|
fi
|
||||||
$(call cp,cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs,$(DESTDIR)/usr/bin/startos-backup-fs)
|
$(call cp,cargo-deps/$(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)
|
||||||
|
|
||||||
@@ -149,10 +159,6 @@ install: $(ALL_TARGETS)
|
|||||||
$(call cp,GIT_HASH.txt,$(DESTDIR)/usr/lib/startos/GIT_HASH.txt)
|
$(call cp,GIT_HASH.txt,$(DESTDIR)/usr/lib/startos/GIT_HASH.txt)
|
||||||
$(call cp,VERSION.txt,$(DESTDIR)/usr/lib/startos/VERSION.txt)
|
$(call cp,VERSION.txt,$(DESTDIR)/usr/lib/startos/VERSION.txt)
|
||||||
|
|
||||||
$(call mkdir,$(DESTDIR)/usr/lib/startos/system-images)
|
|
||||||
$(call cp,system-images/compat/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/compat.tar)
|
|
||||||
$(call cp,system-images/utils/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/utils.tar)
|
|
||||||
|
|
||||||
$(call cp,firmware/$(PLATFORM),$(DESTDIR)/usr/lib/startos/firmware)
|
$(call cp,firmware/$(PLATFORM),$(DESTDIR)/usr/lib/startos/firmware)
|
||||||
|
|
||||||
update-overlay: $(ALL_TARGETS)
|
update-overlay: $(ALL_TARGETS)
|
||||||
@@ -164,10 +170,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/release/startbox
|
wormhole: core/target/$(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/release/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/$(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:"
|
||||||
@@ -187,10 +193,10 @@ update: $(ALL_TARGETS)
|
|||||||
$(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/release/startbox # only update binary (faster than full update)
|
update-startbox: core/target/$(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/release/startbox,/media/startos/next/usr/bin/startbox)
|
$(call cp,core/target/$(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
|
||||||
@@ -235,20 +241,20 @@ sdk/base/lib/osBindings/index.ts: $(shell if [ "$(REBUILD_TYPES)" -ne 0 ]; then
|
|||||||
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
|
touch sdk/base/lib/osBindings/index.ts
|
||||||
|
|
||||||
core/startos/bindings/index.ts: $(shell git 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
|
||||||
./core/build-ts.sh
|
./core/build-ts.sh
|
||||||
ls core/startos/bindings/*.ts | sed 's/core\/startos\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' | grep -v '"./index"' | tee core/startos/bindings/index.ts
|
ls core/startos/bindings/*.ts | sed 's/core\/startos\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' | grep -v '"./index"' | tee core/startos/bindings/index.ts
|
||||||
npm --prefix sdk exec -- prettier --config ./sdk/base/package.json -w ./core/startos/bindings/*.ts
|
npm --prefix sdk exec -- prettier --config ./sdk/base/package.json -w ./core/startos/bindings/*.ts
|
||||||
touch core/startos/bindings/index.ts
|
touch core/startos/bindings/index.ts
|
||||||
|
|
||||||
sdk/dist/package.json sdk/baseDist/package.json: $(shell git ls-files sdk) sdk/base/lib/osBindings/index.ts
|
sdk/dist/package.json sdk/baseDist/package.json: $(call ls-files, sdk) sdk/base/lib/osBindings/index.ts
|
||||||
(cd sdk && make bundle)
|
(cd sdk && make bundle)
|
||||||
touch sdk/dist/package.json
|
touch sdk/dist/package.json
|
||||||
touch sdk/baseDist/package.json
|
touch sdk/baseDist/package.json
|
||||||
|
|
||||||
# TODO: make container-runtime its own makefile?
|
# TODO: make container-runtime its own makefile?
|
||||||
container-runtime/dist/index.js: container-runtime/node_modules/.package-lock.json $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
container-runtime/dist/index.js: container-runtime/node_modules/.package-lock.json $(call ls-files, container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
||||||
npm --prefix container-runtime run build
|
npm --prefix container-runtime run build
|
||||||
|
|
||||||
container-runtime/dist/node_modules/.package-lock.json container-runtime/dist/package.json container-runtime/dist/package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist/package.json container-runtime/install-dist-deps.sh
|
container-runtime/dist/node_modules/.package-lock.json container-runtime/dist/package.json container-runtime/dist/package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist/package.json container-runtime/install-dist-deps.sh
|
||||||
@@ -258,24 +264,15 @@ container-runtime/dist/node_modules/.package-lock.json container-runtime/dist/pa
|
|||||||
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/$(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: build/dpkg-deps/*
|
build/lib/depends build/lib/conflicts: $(ENVIRONMENT_FILE) build/dpkg-deps/*
|
||||||
build/dpkg-deps/generate.sh
|
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)
|
||||||
|
|
||||||
system-images/compat/docker-images/$(ARCH).tar: $(COMPAT_SRC)
|
core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE)
|
||||||
cd system-images/compat && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
ARCH=$(ARCH) PROFILE=$(PROFILE) ./core/build-startbox.sh
|
||||||
|
touch core/target/$(ARCH)-unknown-linux-musl/$(PROFILE)/startbox
|
||||||
system-images/utils/docker-images/$(ARCH).tar: $(UTILS_SRC)
|
|
||||||
cd system-images/utils && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
|
||||||
|
|
||||||
system-images/binfmt/docker-images/$(ARCH).tar: $(BINFMT_SRC)
|
|
||||||
cd system-images/binfmt && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
|
||||||
|
|
||||||
core/target/$(ARCH)-unknown-linux-musl/release/startbox: $(CORE_SRC) $(COMPRESSED_WEB_UIS) web/patchdb-ui-seed.json $(ENVIRONMENT_FILE)
|
|
||||||
ARCH=$(ARCH) ./core/build-startbox.sh
|
|
||||||
touch core/target/$(ARCH)-unknown-linux-musl/release/startbox
|
|
||||||
|
|
||||||
core/target/$(ARCH)-unknown-linux-musl/release/containerbox: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
core/target/$(ARCH)-unknown-linux-musl/release/containerbox: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
ARCH=$(ARCH) ./core/build-containerbox.sh
|
ARCH=$(ARCH) ./core/build-containerbox.sh
|
||||||
@@ -339,3 +336,6 @@ cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console:
|
|||||||
|
|
||||||
cargo-deps/$(ARCH)-unknown-linux-musl/release/startos-backup-fs:
|
cargo-deps/$(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:
|
||||||
|
ARCH=$(ARCH) PREINSTALL="apk add musl-dev pkgconfig" ./build-cargo-dep.sh flamegraph
|
||||||
@@ -8,8 +8,8 @@ IFS="-" read -ra FEATURES <<< "$ENVIRONMENT"
|
|||||||
|
|
||||||
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 }
|
||||||
'
|
'
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
+ gdb
|
+ gdb
|
||||||
+ heaptrack
|
+ heaptrack
|
||||||
|
+ linux-perf
|
||||||
145
build/lib/motd
145
build/lib/motd
@@ -1,34 +1,123 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
printf "\n"
|
|
||||||
printf "Welcome to\n"
|
|
||||||
cat << "ASCII"
|
|
||||||
|
|
||||||
███████
|
parse_essential_db_info() {
|
||||||
█ █ █
|
DB_DUMP="/tmp/startos_db.json"
|
||||||
█ █ █ █
|
|
||||||
█ █ █ █
|
|
||||||
█ █ █ █
|
|
||||||
█ █ █ █
|
|
||||||
█ █
|
|
||||||
███████
|
|
||||||
|
|
||||||
_____ __ ___ __ __
|
if command -v start-cli >/dev/null 2>&1; then
|
||||||
(_ | /\ |__) | / \(_
|
start-cli db dump > "$DB_DUMP" 2>/dev/null || return 1
|
||||||
__) | / \| \ | \__/__)
|
|
||||||
ASCII
|
|
||||||
printf " v$(cat /usr/lib/startos/VERSION.txt)\n\n"
|
|
||||||
printf " %s (%s %s)\n" "$(uname -o)" "$(uname -r)" "$(uname -m)"
|
|
||||||
printf " Git Hash: $(cat /usr/lib/startos/GIT_HASH.txt)"
|
|
||||||
if [ -n "$(cat /usr/lib/startos/ENVIRONMENT.txt)" ]; then
|
|
||||||
printf " ~ $(cat /usr/lib/startos/ENVIRONMENT.txt)\n"
|
|
||||||
else
|
else
|
||||||
printf "\n"
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf "\n"
|
if command -v jq >/dev/null 2>&1 && [ -f "$DB_DUMP" ]; then
|
||||||
printf " * Documentation: https://docs.start9.com\n"
|
HOSTNAME=$(jq -r '.value.serverInfo.hostname // "unknown"' "$DB_DUMP" 2>/dev/null)
|
||||||
printf " * Management: https://%s.local\n" "$(hostname)"
|
VERSION=$(jq -r '.value.serverInfo.version // "unknown"' "$DB_DUMP" 2>/dev/null)
|
||||||
printf " * Support: https://start9.com/contact\n"
|
RAM_BYTES=$(jq -r '.value.serverInfo.ram // 0' "$DB_DUMP" 2>/dev/null)
|
||||||
printf " * Source Code: https://github.com/Start9Labs/start-os\n"
|
WAN_IP=$(jq -r '.value.serverInfo.network.gateways[].ipInfo.wanIp // "unknown"' "$DB_DUMP" 2>/dev/null | head -1)
|
||||||
printf " * License: MIT\n"
|
NTP_SYNCED=$(jq -r '.value.serverInfo.ntpSynced // false' "$DB_DUMP" 2>/dev/null)
|
||||||
printf "\n"
|
|
||||||
|
if [ "$RAM_BYTES" != "0" ] && [ "$RAM_BYTES" != "null" ]; then
|
||||||
|
RAM_GB=$(echo "scale=1; $RAM_BYTES / 1073741824" | bc 2>/dev/null || echo "unknown")
|
||||||
|
else
|
||||||
|
RAM_GB="unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUNNING_SERVICES=$(jq -r '[.value.packageData[] | select(.status.main == "running")] | length' "$DB_DUMP" 2>/dev/null)
|
||||||
|
TOTAL_SERVICES=$(jq -r '.value.packageData | length' "$DB_DUMP" 2>/dev/null)
|
||||||
|
|
||||||
|
rm -f "$DB_DUMP"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
rm -f "$DB_DUMP" 2>/dev/null
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
DB_INFO_AVAILABLE=0
|
||||||
|
if parse_essential_db_info; then
|
||||||
|
DB_INFO_AVAILABLE=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$VERSION" != "unknown" ]; then
|
||||||
|
version_display="v$VERSION"
|
||||||
|
else
|
||||||
|
version_display="v$(cat /usr/lib/startos/VERSION.txt 2>/dev/null || echo 'unknown')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\n\033[1;37m ▄▄▀▀▀▀▀▄▄\033[0m\n"
|
||||||
|
printf "\033[1;37m ▄▀ ▄ ▀▄ ▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄ ▄▄▄▄▄ ▄▄▄▄▄▄▄ \033[1;31m▄██████▄ ▄██████\033[0m\n"
|
||||||
|
printf "\033[1;37m █ █ █ █ █ █ █ █ █ ▀▄ █ \033[1;31m██ ██ ██ \033[0m\n"
|
||||||
|
printf "\033[1;37m█ █ █ █ ▀▄▄▄▄ █ █ █ █ ▄▄▄▀ █ \033[1;31m██ ██ ▀█████▄\033[0m\n"
|
||||||
|
printf "\033[1;37m█ █ █ █ █ █ █ █ █ ▀▄ █ \033[1;31m██ ██ ██\033[0m\n"
|
||||||
|
printf "\033[1;37m █ █ █ █ ▄▄▄▄▄▀ █ █ █ █ ▀▄ █ \033[1;31m▀██████▀ ██████▀\033[0m\n"
|
||||||
|
printf "\033[1;37m █ █\033[0m\n"
|
||||||
|
printf "\033[1;37m ▀▀▄▄▄▀▀ $version_display\033[0m\n\n"
|
||||||
|
|
||||||
|
uptime_str=$(uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}' | sed 's/^ *//')
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$RAM_GB" != "unknown" ]; then
|
||||||
|
memory_used=$(free -m | awk 'NR==2{printf "%.0fMB", $3}')
|
||||||
|
memory_display="$memory_used / ${RAM_GB}GB"
|
||||||
|
else
|
||||||
|
memory_display=$(free -m | awk 'NR==2{printf "%.0fMB / %.0fMB", $3, $2}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
root_usage=$(df -h / | awk 'NR==2{printf "%s (%s free)", $5, $4}')
|
||||||
|
|
||||||
|
if [ -d "/media/startos/data/package-data" ]; then
|
||||||
|
data_usage=$(df -h /media/startos/data/package-data | awk 'NR==2{printf "%s (%s free)", $5, $4}')
|
||||||
|
else
|
||||||
|
data_usage="N/A"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ]; then
|
||||||
|
services_text="$RUNNING_SERVICES/$TOTAL_SERVICES running"
|
||||||
|
else
|
||||||
|
services_text="Unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local_ip=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)
|
||||||
|
if [ -z "$local_ip" ]; then local_ip="N/A"; fi
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$WAN_IP" != "unknown" ]; then
|
||||||
|
wan_ip="$WAN_IP"
|
||||||
|
else
|
||||||
|
wan_ip="N/A"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf " \033[1;37m┌─ SYSTEM STATUS ───────────────────────────────────────────────────┐\033[0m\n"
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Uptime:" "$uptime_str" "Memory:" "$memory_display"
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Root:" "$root_usage" "Data:" "$data_usage"
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ]; then
|
||||||
|
if [ "$RUNNING_SERVICES" -eq "$TOTAL_SERVICES" ] && [ "$TOTAL_SERVICES" -gt 0 ]; then
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;32m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Services:" "$services_text" "WAN:" "$wan_ip"
|
||||||
|
elif [ "$RUNNING_SERVICES" -gt 0 ]; then
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Services:" "$services_text" "WAN:" "$wan_ip"
|
||||||
|
else
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;31m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Services:" "$services_text" "WAN:" "$wan_ip"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;37m%-22s\033[0m %-8s \033[0;33m%-23s\033[0m \033[1;37m│\033[0m\n" "Services:" "$services_text" "WAN:" "$wan_ip"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$NTP_SYNCED" = "true" ]; then
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;32m%-23s\033[0m \033[1;37m│\033[0m\n" "Local:" "$local_ip" "NTP:" "Synced"
|
||||||
|
elif [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$NTP_SYNCED" = "false" ]; then
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;31m%-23s\033[0m \033[1;37m│\033[0m\n" "Local:" "$local_ip" "NTP:" "Not Synced"
|
||||||
|
else
|
||||||
|
printf " \033[1;37m│\033[0m %-8s \033[0;33m%-22s\033[0m %-8s \033[0;37m%-23s\033[0m \033[1;37m│\033[0m\n" "Local:" "$local_ip" "NTP:" "Unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf " \033[1;37m└───────────────────────────────────────────────────────────────────┘\033[0m"
|
||||||
|
|
||||||
|
if [ "$DB_INFO_AVAILABLE" -eq 1 ] && [ "$HOSTNAME" != "unknown" ]; then
|
||||||
|
web_url="https://$HOSTNAME.local"
|
||||||
|
else
|
||||||
|
web_url="https://$(hostname).local"
|
||||||
|
fi
|
||||||
|
printf "\n \033[1;37m┌──────────────────────────────────────────────────── QUICK ACCESS ─┐\033[0m\n"
|
||||||
|
printf " \033[1;37m│\033[0m Web Interface: \033[0;36m%-50s\033[0m \033[1;37m│\033[0m\n" "$web_url"
|
||||||
|
printf " \033[1;37m│\033[0m Documentation: \033[0;36m%-50s\033[0m \033[1;37m│\033[0m\n" "https://staging.docs.start9.com"
|
||||||
|
printf " \033[1;37m│\033[0m Support: \033[0;36m%-50s\033[0m \033[1;37m│\033[0m\n" "https://start9.com/contact"
|
||||||
|
printf " \033[1;37m└───────────────────────────────────────────────────────────────────┘\033[0m\n\n"
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ Description=StartOS Container Runtime Failure Handler
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/bin/start-cli rebuild
|
ExecStart=/usr/bin/start-container rebuild
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ source ~/.bashrc
|
|||||||
nvm install 22
|
nvm install 22
|
||||||
ln -s $(which node) /usr/bin/node
|
ln -s $(which node) /usr/bin/node
|
||||||
|
|
||||||
|
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
|
||||||
sed -i '/\(^\|#\)Compress=/c\Compress=yes' /etc/systemd/journald.conf
|
sed -i '/\(^\|#\)Compress=/c\Compress=yes' /etc/systemd/journald.conf
|
||||||
sed -i '/\(^\|#\)SystemMaxUse=/c\SystemMaxUse=1G' /etc/systemd/journald.conf
|
sed -i '/\(^\|#\)SystemMaxUse=/c\SystemMaxUse=1G' /etc/systemd/journald.conf
|
||||||
@@ -21,3 +23,6 @@ sed -i '/\(^\|#\)ForwardToSyslog=/c\ForwardToSyslog=no' /etc/systemd/journald.co
|
|||||||
systemctl enable container-runtime.service
|
systemctl enable container-runtime.service
|
||||||
|
|
||||||
rm -rf /run/systemd
|
rm -rf /run/systemd
|
||||||
|
|
||||||
|
rm /etc/resolv.conf
|
||||||
|
echo "nameserver 10.0.3.1" > /etc/resolv.conf
|
||||||
2
container-runtime/package-lock.json
generated
2
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.36",
|
"version": "0.4.0-beta.38",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ const SOCKET_PATH = "/media/startos/rpc/host.sock"
|
|||||||
let hostSystemId = 0
|
let hostSystemId = 0
|
||||||
|
|
||||||
export type EffectContext = {
|
export type EffectContext = {
|
||||||
procedureId: string | null
|
eventId: string | null
|
||||||
callbacks?: CallbackHolder
|
callbacks?: CallbackHolder
|
||||||
constRetry?: () => void
|
constRetry?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const rpcRoundFor =
|
const rpcRoundFor =
|
||||||
(procedureId: string | null) =>
|
(eventId: string | null) =>
|
||||||
<K extends T.EffectMethod | "clearCallbacks">(
|
<K extends T.EffectMethod | "clearCallbacks">(
|
||||||
method: K,
|
method: K,
|
||||||
params: Record<string, unknown>,
|
params: Record<string, unknown>,
|
||||||
@@ -52,7 +52,7 @@ const rpcRoundFor =
|
|||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
id,
|
id,
|
||||||
method,
|
method,
|
||||||
params: { ...params, procedureId: procedureId || undefined },
|
params: { ...params, eventId: eventId ?? undefined },
|
||||||
}) + "\n",
|
}) + "\n",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -103,8 +103,9 @@ const rpcRoundFor =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function makeEffects(context: EffectContext): Effects {
|
export function makeEffects(context: EffectContext): Effects {
|
||||||
const rpcRound = rpcRoundFor(context.procedureId)
|
const rpcRound = rpcRoundFor(context.eventId)
|
||||||
const self: Effects = {
|
const self: Effects = {
|
||||||
|
eventId: context.eventId,
|
||||||
child: (name) =>
|
child: (name) =>
|
||||||
makeEffects({ ...context, callbacks: context.callbacks?.child(name) }),
|
makeEffects({ ...context, callbacks: context.callbacks?.child(name) }),
|
||||||
constRetry: context.constRetry,
|
constRetry: context.constRetry,
|
||||||
|
|||||||
@@ -242,11 +242,11 @@ export class RpcListener {
|
|||||||
.when(runType, async ({ id, params }) => {
|
.when(runType, async ({ id, params }) => {
|
||||||
const system = this.system
|
const system = this.system
|
||||||
const procedure = jsonPath.unsafeCast(params.procedure)
|
const procedure = jsonPath.unsafeCast(params.procedure)
|
||||||
const { input, timeout, id: procedureId } = params
|
const { input, timeout, id: eventId } = params
|
||||||
const result = this.getResult(
|
const result = this.getResult(
|
||||||
procedure,
|
procedure,
|
||||||
system,
|
system,
|
||||||
procedureId,
|
eventId,
|
||||||
timeout,
|
timeout,
|
||||||
input,
|
input,
|
||||||
)
|
)
|
||||||
@@ -256,11 +256,11 @@ export class RpcListener {
|
|||||||
.when(sandboxRunType, async ({ id, params }) => {
|
.when(sandboxRunType, async ({ id, params }) => {
|
||||||
const system = this.system
|
const system = this.system
|
||||||
const procedure = jsonPath.unsafeCast(params.procedure)
|
const procedure = jsonPath.unsafeCast(params.procedure)
|
||||||
const { input, timeout, id: procedureId } = params
|
const { input, timeout, id: eventId } = params
|
||||||
const result = this.getResult(
|
const result = this.getResult(
|
||||||
procedure,
|
procedure,
|
||||||
system,
|
system,
|
||||||
procedureId,
|
eventId,
|
||||||
timeout,
|
timeout,
|
||||||
input,
|
input,
|
||||||
)
|
)
|
||||||
@@ -275,7 +275,7 @@ export class RpcListener {
|
|||||||
const callbacks =
|
const callbacks =
|
||||||
this.callbacks?.getChild("main") || this.callbacks?.child("main")
|
this.callbacks?.getChild("main") || this.callbacks?.child("main")
|
||||||
const effects = makeEffects({
|
const effects = makeEffects({
|
||||||
procedureId: null,
|
eventId: null,
|
||||||
callbacks,
|
callbacks,
|
||||||
})
|
})
|
||||||
return handleRpc(
|
return handleRpc(
|
||||||
@@ -304,7 +304,7 @@ export class RpcListener {
|
|||||||
}
|
}
|
||||||
await this._system.exit(
|
await this._system.exit(
|
||||||
makeEffects({
|
makeEffects({
|
||||||
procedureId: params.id,
|
eventId: params.id,
|
||||||
}),
|
}),
|
||||||
target,
|
target,
|
||||||
)
|
)
|
||||||
@@ -320,14 +320,14 @@ export class RpcListener {
|
|||||||
const system = await this.getDependencies.system()
|
const system = await this.getDependencies.system()
|
||||||
this.callbacks = new CallbackHolder(
|
this.callbacks = new CallbackHolder(
|
||||||
makeEffects({
|
makeEffects({
|
||||||
procedureId: params.id,
|
eventId: params.id,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const callbacks = this.callbacks.child("init")
|
const callbacks = this.callbacks.child("init")
|
||||||
console.error("Initializing...")
|
console.error("Initializing...")
|
||||||
await system.init(
|
await system.init(
|
||||||
makeEffects({
|
makeEffects({
|
||||||
procedureId: params.id,
|
eventId: params.id,
|
||||||
callbacks,
|
callbacks,
|
||||||
}),
|
}),
|
||||||
params.kind,
|
params.kind,
|
||||||
@@ -399,7 +399,7 @@ export class RpcListener {
|
|||||||
private getResult(
|
private getResult(
|
||||||
procedure: typeof jsonPath._TYPE,
|
procedure: typeof jsonPath._TYPE,
|
||||||
system: System,
|
system: System,
|
||||||
procedureId: string,
|
eventId: string,
|
||||||
timeout: number | null | undefined,
|
timeout: number | null | undefined,
|
||||||
input: any,
|
input: any,
|
||||||
) {
|
) {
|
||||||
@@ -410,7 +410,7 @@ export class RpcListener {
|
|||||||
}
|
}
|
||||||
const callbacks = this.callbacks?.child(procedure)
|
const callbacks = this.callbacks?.child(procedure)
|
||||||
const effects = makeEffects({
|
const effects = makeEffects({
|
||||||
procedureId,
|
eventId,
|
||||||
callbacks,
|
callbacks,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -509,13 +509,18 @@ export class SystemForEmbassy implements System {
|
|||||||
): Promise<T.ActionInput | null> {
|
): Promise<T.ActionInput | null> {
|
||||||
if (actionId === "config") {
|
if (actionId === "config") {
|
||||||
const config = await this.getConfig(effects, timeoutMs)
|
const config = await this.getConfig(effects, timeoutMs)
|
||||||
return { spec: config.spec, value: config.config }
|
return {
|
||||||
|
eventId: effects.eventId!,
|
||||||
|
spec: config.spec,
|
||||||
|
value: config.config,
|
||||||
|
}
|
||||||
} else if (actionId === "properties") {
|
} else if (actionId === "properties") {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
const oldSpec = this.manifest.actions?.[actionId]?.["input-spec"]
|
const oldSpec = this.manifest.actions?.[actionId]?.["input-spec"]
|
||||||
if (!oldSpec) return null
|
if (!oldSpec) return null
|
||||||
return {
|
return {
|
||||||
|
eventId: effects.eventId!,
|
||||||
spec: transformConfigSpec(oldSpec as OldConfigSpec),
|
spec: transformConfigSpec(oldSpec as OldConfigSpec),
|
||||||
value: null,
|
value: null,
|
||||||
}
|
}
|
||||||
@@ -1233,14 +1238,14 @@ async function updateConfig(
|
|||||||
const url: string =
|
const url: string =
|
||||||
filled === null || filled.addressInfo === null
|
filled === null || filled.addressInfo === null
|
||||||
? ""
|
? ""
|
||||||
: catchFn(() =>
|
: catchFn(
|
||||||
utils.hostnameInfoToAddress(
|
() =>
|
||||||
specValue.target === "lan-address"
|
(specValue.target === "lan-address"
|
||||||
? filled.addressInfo!.localHostnames[0] ||
|
? filled.addressInfo!.localHostnames[0] ||
|
||||||
filled.addressInfo!.onionHostnames[0]
|
filled.addressInfo!.onionHostnames[0]
|
||||||
: filled.addressInfo!.onionHostnames[0] ||
|
: filled.addressInfo!.onionHostnames[0] ||
|
||||||
filled.addressInfo!.localHostnames[0],
|
filled.addressInfo!.localHostnames[0]
|
||||||
),
|
).hostname.value,
|
||||||
) || ""
|
) || ""
|
||||||
mutConfigValue[key] = url
|
mutConfigValue[key] = url
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,10 @@ 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-cli
|
sudo cp ../core/target/$ARCH-unknown-linux-musl/release/containerbox tmp/combined/usr/bin/start-container
|
||||||
sudo chown 0:0 tmp/combined/usr/bin/start-cli
|
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 chown 0:0 tmp/combined/usr/bin/start-container
|
||||||
echo container-runtime | sha256sum | head -c 32 | cat - <(echo) | sudo tee tmp/combined/etc/machine-id
|
echo container-runtime | sha256sum | head -c 32 | cat - <(echo) | sudo tee tmp/combined/etc/machine-id
|
||||||
cat deb-install.sh | sudo systemd-nspawn --console=pipe -D tmp/combined $QEMU /bin/bash
|
cat deb-install.sh | sudo systemd-nspawn --console=pipe -D tmp/combined $QEMU /bin/bash
|
||||||
sudo truncate -s 0 tmp/combined/etc/machine-id
|
sudo truncate -s 0 tmp/combined/etc/machine-id
|
||||||
|
|||||||
3627
core/Cargo.lock
generated
3627
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -5,18 +5,18 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
|||||||
set -ea
|
set -ea
|
||||||
shopt -s expand_aliases
|
shopt -s expand_aliases
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
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="$ARCH-unknown-linux-musl"
|
||||||
elif [ "$KERNEL_NAME" = "Darwin" ]; then
|
elif [ "$KERNEL_NAME" = "Darwin" ]; then
|
||||||
@@ -33,22 +33,32 @@ if tty -s; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
|
||||||
RUSTFLAGS=""
|
|
||||||
|
|
||||||
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
# Ensure GIT_HASH.txt exists if not created by higher-level build steps
|
||||||
|
if [ ! -f GIT_HASH.txt ] && command -v git >/dev/null 2>&1; then
|
||||||
|
git rev-parse HEAD > GIT_HASH.txt || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
FEATURES="$(echo "${ENVIRONMENT:-}" | sed 's/-/,/g')"
|
||||||
|
FEATURE_ARGS="cli"
|
||||||
|
if [ -n "$FEATURES" ]; then
|
||||||
|
FEATURE_ARGS="$FEATURE_ARGS,$FEATURES"
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUSTFLAGS=""
|
||||||
|
if [[ "${ENVIRONMENT:-}" =~ (^|-)unstable($|-) ]]; then
|
||||||
RUSTFLAGS="--cfg tokio_unstable"
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if which zig > /dev/null && [ "$ENFORCE_USE_DOCKER" != 1 ]; do
|
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\""
|
||||||
RUSTFLAGS=$RUSTFLAGS sh -c "cd core && cargo zigbuild --release --no-default-features --features cli,$FEATURES --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"
|
||||||
else
|
else
|
||||||
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'
|
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 cli,$FEATURES --locked --bin start-cli --target=$TARGET"
|
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
|
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"
|
rust-zig-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -26,11 +26,11 @@ if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; 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'
|
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 container-runtime,$FEATURES --locked --bin containerbox --target=$ARCH-unknown-linux-musl"
|
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"
|
||||||
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/containerbox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/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-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
@@ -26,11 +26,11 @@ if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; 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'
|
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,$FEATURES --locked --bin registrybox --target=$ARCH-unknown-linux-musl"
|
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"
|
||||||
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/registrybox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/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-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
PROFILE=${PROFILE:-release}
|
||||||
|
if [ "${PROFILE}" = "release" ]; then
|
||||||
|
BUILD_FLAGS="--release"
|
||||||
|
fi
|
||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
set -ea
|
set -ea
|
||||||
@@ -26,11 +31,11 @@ if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; 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'
|
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,daemon,$FEATURES --locked --bin startbox --target=$ARCH-unknown-linux-musl"
|
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"
|
||||||
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/startbox | awk '{ print $3 }')" != "$UID" ]; then
|
if [ "$(ls -nd core/target/$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-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
fi
|
fi
|
||||||
@@ -26,7 +26,7 @@ if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; 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'
|
source ./core/builder-alias.sh
|
||||||
|
|
||||||
echo "FEATURES=\"$FEATURES\""
|
echo "FEATURES=\"$FEATURES\""
|
||||||
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
echo "RUSTFLAGS=\"$RUSTFLAGS\""
|
||||||
|
|||||||
36
core/build-tunnelbox.sh
Executable file
36
core/build-tunnelbox.sh
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
set -ea
|
||||||
|
shopt -s expand_aliases
|
||||||
|
|
||||||
|
if [ -z "$ARCH" ]; then
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$ARCH" = "arm64" ]; then
|
||||||
|
ARCH="aarch64"
|
||||||
|
fi
|
||||||
|
|
||||||
|
USE_TTY=
|
||||||
|
if tty -s; then
|
||||||
|
USE_TTY="-it"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
|
||||||
|
RUSTFLAGS=""
|
||||||
|
|
||||||
|
if [[ "${ENVIRONMENT}" =~ (^|-)unstable($|-) ]]; then
|
||||||
|
RUSTFLAGS="--cfg tokio_unstable"
|
||||||
|
fi
|
||||||
|
|
||||||
|
source ./core/builder-alias.sh
|
||||||
|
|
||||||
|
echo "FEATURES=\"$FEATURES\""
|
||||||
|
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"
|
||||||
|
if [ "$(ls -nd core/target/$ARCH-unknown-linux-musl/release/tunnelbox | awk '{ print $3 }')" != "$UID" ]; then
|
||||||
|
rust-musl-builder sh -c "cd core && chown -R $UID:$UID target && chown -R $UID:$UID /root/.cargo"
|
||||||
|
fi
|
||||||
3
core/builder-alias.sh
Normal file
3
core/builder-alias.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/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'
|
||||||
@@ -16,4 +16,4 @@ if [ "$PLATFORM" = "arm64" ]; then
|
|||||||
PLATFORM="aarch64"
|
PLATFORM="aarch64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cargo install --path=./startos --no-default-features --features=cli,docker,registry --bin start-cli --locked
|
cargo install --path=./startos --no-default-features --features=cli,docker --bin start-cli --locked
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
arti-client = { version = "0.33", default-features = false, git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
||||||
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"
|
||||||
@@ -29,16 +30,10 @@ rpc-toolkit = { git = "https://github.com/Start9Labs/rpc-toolkit.git", branch =
|
|||||||
rustls = "0.23"
|
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"
|
||||||
sqlx = { version = "0.8.6", features = [
|
|
||||||
"chrono",
|
|
||||||
"runtime-tokio-rustls",
|
|
||||||
"postgres",
|
|
||||||
] }
|
|
||||||
ssh-key = "0.6.2"
|
ssh-key = "0.6.2"
|
||||||
ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8"
|
ts-rs = "9"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
torut = { git = "https://github.com/Start9Labs/torut.git", branch = "update/dependencies" }
|
|
||||||
tracing = "0.1.39"
|
tracing = "0.1.39"
|
||||||
yasi = "0.1.5"
|
yasi = { version = "0.1.6", features = ["serde", "ts-rs"] }
|
||||||
zbus = "5"
|
zbus = "5"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
@@ -14,28 +15,26 @@ use crate::{mime, Error, ErrorKind, ResultExt};
|
|||||||
#[derive(Clone, TS)]
|
#[derive(Clone, TS)]
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub struct DataUrl<'a> {
|
pub struct DataUrl<'a> {
|
||||||
mime: InternedString,
|
pub mime: InternedString,
|
||||||
data: Cow<'a, [u8]>,
|
pub data: Cow<'a, [u8]>,
|
||||||
}
|
}
|
||||||
impl<'a> DataUrl<'a> {
|
impl<'a> DataUrl<'a> {
|
||||||
pub const DEFAULT_MIME: &'static str = "application/octet-stream";
|
pub const DEFAULT_MIME: &'static str = "application/octet-stream";
|
||||||
pub const MAX_SIZE: u64 = 100 * 1024;
|
pub const MAX_SIZE: u64 = 100 * 1024;
|
||||||
|
|
||||||
// data:{mime};base64,{data}
|
fn to_string(&self) -> String {
|
||||||
pub fn to_string(&self) -> String {
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut res = String::with_capacity(self.data_url_len_without_mime() + self.mime.len());
|
let mut res = String::with_capacity(self.len());
|
||||||
let _ = write!(res, "data:{};base64,", self.mime);
|
write!(&mut res, "{self}").unwrap();
|
||||||
base64::engine::general_purpose::STANDARD.encode_string(&self.data, &mut res);
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_url_len_without_mime(&self) -> usize {
|
fn len_without_mime(&self) -> usize {
|
||||||
5 + 8 + (4 * self.data.len() / 3) + 3
|
5 + 8 + (4 * self.data.len() / 3) + 3
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data_url_len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.data_url_len_without_mime() + self.mime.len()
|
self.len_without_mime() + self.mime.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_slice(mime: &str, data: &'a [u8]) -> Self {
|
pub fn from_slice(mime: &str, data: &'a [u8]) -> Self {
|
||||||
@@ -44,6 +43,10 @@ impl<'a> DataUrl<'a> {
|
|||||||
data: Cow::Borrowed(data),
|
data: Cow::Borrowed(data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn canonical_ext(&self) -> Option<&'static str> {
|
||||||
|
mime::unmime(&self.mime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl DataUrl<'static> {
|
impl DataUrl<'static> {
|
||||||
pub async fn from_reader(
|
pub async fn from_reader(
|
||||||
@@ -109,12 +112,57 @@ impl DataUrl<'static> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> std::fmt::Display for DataUrl<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"data:{};base64,{}",
|
||||||
|
self.mime,
|
||||||
|
base64::display::Base64Display::new(
|
||||||
|
&*self.data,
|
||||||
|
&base64::engine::general_purpose::STANDARD
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<'a> std::fmt::Debug for DataUrl<'a> {
|
impl<'a> std::fmt::Debug for DataUrl<'a> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(&self.to_string())
|
std::fmt::Display::fmt(self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DataUrlParseError;
|
||||||
|
impl std::fmt::Display for DataUrlParseError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "invalid base64 url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for DataUrlParseError {}
|
||||||
|
impl From<DataUrlParseError> for Error {
|
||||||
|
fn from(e: DataUrlParseError) -> Self {
|
||||||
|
Error::new(e, ErrorKind::ParseUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for DataUrl<'static> {
|
||||||
|
type Err = DataUrlParseError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
s.strip_prefix("data:")
|
||||||
|
.and_then(|v| v.split_once(";base64,"))
|
||||||
|
.and_then(|(mime, data)| {
|
||||||
|
Some(DataUrl {
|
||||||
|
mime: InternedString::intern(mime),
|
||||||
|
data: Cow::Owned(
|
||||||
|
base64::engine::general_purpose::STANDARD
|
||||||
|
.decode(data)
|
||||||
|
.ok()?,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.ok_or(DataUrlParseError)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<'de> Deserialize<'de> for DataUrl<'static> {
|
impl<'de> Deserialize<'de> for DataUrl<'static> {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
@@ -130,19 +178,7 @@ impl<'de> Deserialize<'de> for DataUrl<'static> {
|
|||||||
where
|
where
|
||||||
E: serde::de::Error,
|
E: serde::de::Error,
|
||||||
{
|
{
|
||||||
v.strip_prefix("data:")
|
v.parse().map_err(|_| {
|
||||||
.and_then(|v| v.split_once(";base64,"))
|
|
||||||
.and_then(|(mime, data)| {
|
|
||||||
Some(DataUrl {
|
|
||||||
mime: InternedString::intern(mime),
|
|
||||||
data: Cow::Owned(
|
|
||||||
base64::engine::general_purpose::STANDARD
|
|
||||||
.decode(data)
|
|
||||||
.ok()?,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.ok_or_else(|| {
|
|
||||||
E::invalid_value(serde::de::Unexpected::Str(v), &"a valid base64 data url")
|
E::invalid_value(serde::de::Unexpected::Str(v), &"a valid base64 data url")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -168,6 +204,6 @@ fn doesnt_reallocate() {
|
|||||||
mime: InternedString::intern("png"),
|
mime: InternedString::intern("png"),
|
||||||
data: Cow::Borrowed(&random[..i]),
|
data: Cow::Borrowed(&random[..i]),
|
||||||
};
|
};
|
||||||
assert_eq!(icon.to_string().capacity(), icon.data_url_len());
|
assert_eq!(icon.to_string().capacity(), icon.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,11 +288,6 @@ impl From<patch_db::Error> for Error {
|
|||||||
Error::new(e, ErrorKind::Database)
|
Error::new(e, ErrorKind::Database)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<sqlx::Error> for Error {
|
|
||||||
fn from(e: sqlx::Error) -> Self {
|
|
||||||
Error::new(e, ErrorKind::Database)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<ed25519_dalek::SignatureError> for Error {
|
impl From<ed25519_dalek::SignatureError> for Error {
|
||||||
fn from(e: ed25519_dalek::SignatureError) -> Self {
|
fn from(e: ed25519_dalek::SignatureError) -> Self {
|
||||||
Error::new(e, ErrorKind::InvalidSignature)
|
Error::new(e, ErrorKind::InvalidSignature)
|
||||||
@@ -303,11 +298,6 @@ impl From<std::net::AddrParseError> for Error {
|
|||||||
Error::new(e, ErrorKind::ParseNetAddress)
|
Error::new(e, ErrorKind::ParseNetAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<torut::control::ConnError> for Error {
|
|
||||||
fn from(e: torut::control::ConnError) -> Self {
|
|
||||||
Error::new(e, ErrorKind::Tor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<ipnet::AddrParseError> for Error {
|
impl From<ipnet::AddrParseError> for Error {
|
||||||
fn from(e: ipnet::AddrParseError) -> Self {
|
fn from(e: ipnet::AddrParseError) -> Self {
|
||||||
Error::new(e, ErrorKind::ParseNetAddress)
|
Error::new(e, ErrorKind::ParseNetAddress)
|
||||||
@@ -353,8 +343,8 @@ impl From<reqwest::Error> for Error {
|
|||||||
Error::new(e, kind)
|
Error::new(e, kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<torut::onion::OnionAddressParseError> for Error {
|
impl From<arti_client::Error> for Error {
|
||||||
fn from(e: torut::onion::OnionAddressParseError) -> Self {
|
fn from(e: arti_client::Error) -> Self {
|
||||||
Error::new(e, ErrorKind::Tor)
|
Error::new(e, ErrorKind::Tor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
core/models/src/id/gateway.rs
Normal file
60
core/models/src/id/gateway.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use std::convert::Infallible;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
use yasi::InternedString;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TS)]
|
||||||
|
#[ts(type = "string")]
|
||||||
|
pub struct GatewayId(InternedString);
|
||||||
|
impl GatewayId {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&*self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<InternedString> for GatewayId {
|
||||||
|
fn from(value: InternedString) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<GatewayId> for InternedString {
|
||||||
|
fn from(value: GatewayId) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FromStr for GatewayId {
|
||||||
|
type Err = Infallible;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(GatewayId(InternedString::intern(s)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<GatewayId> for GatewayId {
|
||||||
|
fn as_ref(&self) -> &GatewayId {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for GatewayId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", &self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<str> for GatewayId {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<Path> for GatewayId {
|
||||||
|
fn as_ref(&self) -> &Path {
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'de> Deserialize<'de> for GatewayId {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Ok(GatewayId(serde::Deserialize::deserialize(deserializer)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,20 +60,3 @@ impl AsRef<Path> for HostId {
|
|||||||
self.0.as_ref().as_ref()
|
self.0.as_ref().as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for HostId {
|
|
||||||
fn encode_by_ref(
|
|
||||||
&self,
|
|
||||||
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
|
|
||||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
|
||||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl sqlx::Type<sqlx::Postgres> for HostId {
|
|
||||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||||||
use yasi::InternedString;
|
use yasi::InternedString;
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
|
mod gateway;
|
||||||
mod health_check;
|
mod health_check;
|
||||||
mod host;
|
mod host;
|
||||||
mod image;
|
mod image;
|
||||||
@@ -16,6 +17,7 @@ mod service_interface;
|
|||||||
mod volume;
|
mod volume;
|
||||||
|
|
||||||
pub use action::ActionId;
|
pub use action::ActionId;
|
||||||
|
pub use gateway::GatewayId;
|
||||||
pub use health_check::HealthCheckId;
|
pub use health_check::HealthCheckId;
|
||||||
pub use host::HostId;
|
pub use host::HostId;
|
||||||
pub use image::ImageId;
|
pub use image::ImageId;
|
||||||
@@ -116,20 +118,3 @@ impl Serialize for Id {
|
|||||||
serializer.serialize_str(self)
|
serializer.serialize_str(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for Id {
|
|
||||||
fn encode_by_ref(
|
|
||||||
&self,
|
|
||||||
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
|
|
||||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
|
||||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl sqlx::Type<sqlx::Postgres> for Id {
|
|
||||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -87,20 +87,3 @@ impl Serialize for PackageId {
|
|||||||
Serialize::serialize(&self.0, serializer)
|
Serialize::serialize(&self.0, serializer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for PackageId {
|
|
||||||
fn encode_by_ref(
|
|
||||||
&self,
|
|
||||||
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
|
|
||||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
|
||||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl sqlx::Type<sqlx::Postgres> for PackageId {
|
|
||||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -44,23 +44,6 @@ impl AsRef<Path> for ServiceInterfaceId {
|
|||||||
self.0.as_ref().as_ref()
|
self.0.as_ref().as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for ServiceInterfaceId {
|
|
||||||
fn encode_by_ref(
|
|
||||||
&self,
|
|
||||||
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
|
|
||||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
|
||||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl sqlx::Type<sqlx::Postgres> for ServiceInterfaceId {
|
|
||||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
|
||||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromStr for ServiceInterfaceId {
|
impl FromStr for ServiceInterfaceId {
|
||||||
type Err = <Id as FromStr>::Err;
|
type Err = <Id as FromStr>::Err;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||||
description = "The core of StartOS"
|
description = "The core of StartOS"
|
||||||
documentation = "https://docs.rs/start-os"
|
documentation = "https://docs.rs/start-os"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
keywords = [
|
keywords = [
|
||||||
"self-hosted",
|
"self-hosted",
|
||||||
"raspberry-pi",
|
"raspberry-pi",
|
||||||
@@ -14,7 +14,7 @@ keywords = [
|
|||||||
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.9" # VERSION_BUMP
|
version = "0.4.0-alpha.10" # VERSION_BUMP
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@@ -37,18 +37,36 @@ path = "src/main.rs"
|
|||||||
name = "registrybox"
|
name = "registrybox"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tunnelbox"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
cli = []
|
cli = ["cli-startd", "cli-registry", "cli-tunnel"]
|
||||||
container-runtime = ["procfs", "pty-process"]
|
cli-container = ["procfs", "pty-process"]
|
||||||
daemon = ["mail-send"]
|
cli-registry = []
|
||||||
registry = []
|
cli-startd = []
|
||||||
default = ["cli", "daemon", "registry", "container-runtime"]
|
cli-tunnel = []
|
||||||
|
default = ["cli", "startd", "registry", "cli-container", "tunnel"]
|
||||||
dev = []
|
dev = []
|
||||||
unstable = ["console-subscriber", "tokio/tracing"]
|
|
||||||
docker = []
|
docker = []
|
||||||
|
registry = []
|
||||||
|
startd = ["mail-send"]
|
||||||
test = []
|
test = []
|
||||||
|
tunnel = []
|
||||||
|
unstable = ["console-subscriber", "tokio/tracing"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
arti-client = { version = "0.33", features = [
|
||||||
|
"compression",
|
||||||
|
"experimental-api",
|
||||||
|
"rustls",
|
||||||
|
"static",
|
||||||
|
"tokio",
|
||||||
|
"ephemeral-keystore",
|
||||||
|
"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"] }
|
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",
|
||||||
@@ -81,8 +99,9 @@ cookie_store = "0.21.0"
|
|||||||
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"
|
||||||
ed25519 = { version = "2.2.3", features = ["pkcs8", "pem", "alloc"] }
|
ed25519 = { version = "2.2.3", features = ["pkcs8", "pem", "alloc"] }
|
||||||
ed25519-dalek = { version = "2.1.1", features = [
|
ed25519-dalek = { version = "2.2.0", features = [
|
||||||
"serde",
|
"serde",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
@@ -99,6 +118,8 @@ futures = "0.3.28"
|
|||||||
gpt = "4.1.0"
|
gpt = "4.1.0"
|
||||||
helpers = { path = "../helpers" }
|
helpers = { path = "../helpers" }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
hickory-client = "0.25.2"
|
||||||
|
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"
|
||||||
@@ -116,14 +137,14 @@ id-pool = { version = "0.2.2", default-features = false, features = [
|
|||||||
"serde",
|
"serde",
|
||||||
"u16",
|
"u16",
|
||||||
] }
|
] }
|
||||||
imbl = "4.0.1"
|
imbl = { version = "6", features = ["serde", "small-chunks"] }
|
||||||
imbl-value = "0.3.2"
|
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"] }
|
||||||
indexmap = { version = "2.0.2", features = ["serde"] }
|
indexmap = { version = "2.0.2", features = ["serde"] }
|
||||||
indicatif = { version = "0.17.7", features = ["tokio"] }
|
indicatif = { version = "0.17.7", features = ["tokio"] }
|
||||||
|
inotify = "0.11.0"
|
||||||
integer-encoding = { version = "4.0.0", features = ["tokio_async"] }
|
integer-encoding = { version = "4.0.0", features = ["tokio_async"] }
|
||||||
ipnet = { version = "2.8.0", features = ["serde"] }
|
ipnet = { version = "2.8.0", features = ["serde"] }
|
||||||
iprange = { version = "0.6.7", features = ["serde"] }
|
|
||||||
isocountry = "0.3.2"
|
isocountry = "0.3.2"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
jaq-core = "0.10.1"
|
jaq-core = "0.10.1"
|
||||||
@@ -168,7 +189,7 @@ 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"
|
||||||
rand = "0.9.0"
|
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 = ["stream", "json", "socks"] }
|
||||||
reqwest_cookie_store = "0.8.0"
|
reqwest_cookie_store = "0.8.0"
|
||||||
@@ -176,6 +197,7 @@ 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", branch = "master" }
|
||||||
rust-argon2 = "2.0.0"
|
rust-argon2 = "2.0.0"
|
||||||
rustyline-async = "0.4.1"
|
rustyline-async = "0.4.1"
|
||||||
|
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" }
|
||||||
@@ -189,12 +211,12 @@ sha2 = "0.10.2"
|
|||||||
shell-words = "1"
|
shell-words = "1"
|
||||||
signal-hook = "0.3.17"
|
signal-hook = "0.3.17"
|
||||||
simple-logging = "2.0.2"
|
simple-logging = "2.0.2"
|
||||||
socket2 = "0.5.7"
|
socket2 = { version = "0.6.0", features = ["all"] }
|
||||||
|
socks5-impl = { version = "0.7.2", features = ["server"] }
|
||||||
sqlx = { version = "0.8.6", features = [
|
sqlx = { version = "0.8.6", features = [
|
||||||
"chrono",
|
|
||||||
"runtime-tokio-rustls",
|
"runtime-tokio-rustls",
|
||||||
"postgres",
|
"postgres",
|
||||||
] }
|
], 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"
|
||||||
@@ -203,22 +225,33 @@ thiserror = "2.0.12"
|
|||||||
textwrap = "0.16.1"
|
textwrap = "0.16.1"
|
||||||
tokio = { version = "1.38.1", features = ["full"] }
|
tokio = { version = "1.38.1", features = ["full"] }
|
||||||
tokio-rustls = "0.26.0"
|
tokio-rustls = "0.26.0"
|
||||||
tokio-socks = "0.5.1"
|
|
||||||
tokio-stream = { version = "0.1.14", features = ["io-util", "sync", "net"] }
|
tokio-stream = { version = "0.1.14", features = ["io-util", "sync", "net"] }
|
||||||
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"] }
|
||||||
torut = { git = "https://github.com/Start9Labs/torut.git", branch = "update/dependencies", features = [
|
tor-cell = { version = "0.33", git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
||||||
"serialize",
|
tor-hscrypto = { version = "0.33", features = [
|
||||||
] }
|
"full",
|
||||||
|
], 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" }
|
||||||
|
tor-keymgr = { version = "0.33", features = [
|
||||||
|
"ephemeral-keystore",
|
||||||
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
||||||
|
tor-llcrypto = { version = "0.33", features = [
|
||||||
|
"full",
|
||||||
|
], 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" }
|
||||||
|
tor-rtcompat = { version = "0.33", features = [
|
||||||
|
"tokio",
|
||||||
|
"rustls",
|
||||||
|
], git = "https://github.com/Start9Labs/arti.git", branch = "patch/disable-exit" }
|
||||||
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"
|
||||||
tracing-futures = "0.2.5"
|
tracing-futures = "0.2.5"
|
||||||
tracing-journald = "0.3.0"
|
tracing-journald = "0.3.0"
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
trust-dns-server = "0.23.1"
|
ts-rs = "9.0.1"
|
||||||
ts-rs = { git = "https://github.com/dr-bonez/ts-rs.git", branch = "feature/top-level-as" } # "8.1.0"
|
|
||||||
typed-builder = "0.21.0"
|
typed-builder = "0.21.0"
|
||||||
unix-named-pipe = "0.2.0"
|
unix-named-pipe = "0.2.0"
|
||||||
url = { version = "2.4.1", features = ["serde"] }
|
url = { version = "2.4.1", features = ["serde"] }
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use imbl_value::InternedString;
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
use torut::onion::TorSecretKeyV3;
|
|
||||||
|
|
||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::hostname::{generate_hostname, generate_id, Hostname};
|
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
||||||
use crate::net::ssl::{generate_key, make_root_cert};
|
use crate::net::ssl::{generate_key, make_root_cert};
|
||||||
|
use crate::net::tor::TorSecretKey;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
@@ -19,28 +20,28 @@ fn hash_password(password: &str) -> Result<String, Error> {
|
|||||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)
|
.with_kind(crate::ErrorKind::PasswordHashGeneration)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AccountInfo {
|
pub struct AccountInfo {
|
||||||
pub server_id: String,
|
pub server_id: String,
|
||||||
pub hostname: Hostname,
|
pub hostname: Hostname,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub tor_keys: Vec<TorSecretKeyV3>,
|
pub tor_keys: Vec<TorSecretKey>,
|
||||||
pub root_ca_key: PKey<Private>,
|
pub root_ca_key: PKey<Private>,
|
||||||
pub root_ca_cert: X509,
|
pub root_ca_cert: X509,
|
||||||
pub ssh_key: ssh_key::PrivateKey,
|
pub ssh_key: ssh_key::PrivateKey,
|
||||||
pub compat_s9pk_key: ed25519_dalek::SigningKey,
|
pub developer_key: ed25519_dalek::SigningKey,
|
||||||
}
|
}
|
||||||
impl AccountInfo {
|
impl AccountInfo {
|
||||||
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
pub fn new(password: &str, start_time: SystemTime) -> Result<Self, Error> {
|
||||||
let server_id = generate_id();
|
let server_id = generate_id();
|
||||||
let hostname = generate_hostname();
|
let hostname = generate_hostname();
|
||||||
let tor_key = vec![TorSecretKeyV3::generate()];
|
let tor_key = vec![TorSecretKey::generate()];
|
||||||
let root_ca_key = generate_key()?;
|
let root_ca_key = generate_key()?;
|
||||||
let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
|
let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
|
||||||
let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random(
|
let ssh_key = ssh_key::PrivateKey::from(ssh_key::private::Ed25519Keypair::random(
|
||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
));
|
));
|
||||||
let compat_s9pk_key =
|
let developer_key =
|
||||||
ed25519_dalek::SigningKey::generate(&mut ssh_key::rand_core::OsRng::default());
|
ed25519_dalek::SigningKey::generate(&mut ssh_key::rand_core::OsRng::default());
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
server_id,
|
server_id,
|
||||||
@@ -50,7 +51,7 @@ impl AccountInfo {
|
|||||||
root_ca_key,
|
root_ca_key,
|
||||||
root_ca_cert,
|
root_ca_cert,
|
||||||
ssh_key,
|
ssh_key,
|
||||||
compat_s9pk_key,
|
developer_key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ impl AccountInfo {
|
|||||||
let root_ca_key = cert_store.as_root_key().de()?.0;
|
let root_ca_key = cert_store.as_root_key().de()?.0;
|
||||||
let root_ca_cert = cert_store.as_root_cert().de()?.0;
|
let root_ca_cert = cert_store.as_root_cert().de()?.0;
|
||||||
let ssh_key = db.as_private().as_ssh_privkey().de()?.0;
|
let ssh_key = db.as_private().as_ssh_privkey().de()?.0;
|
||||||
let compat_s9pk_key = db.as_private().as_compat_s9pk_key().de()?.0;
|
let compat_s9pk_key = db.as_private().as_developer_key().de()?.0;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
server_id,
|
server_id,
|
||||||
@@ -84,7 +85,7 @@ impl AccountInfo {
|
|||||||
root_ca_key,
|
root_ca_key,
|
||||||
root_ca_cert,
|
root_ca_cert,
|
||||||
ssh_key,
|
ssh_key,
|
||||||
compat_s9pk_key,
|
developer_key: compat_s9pk_key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ impl AccountInfo {
|
|||||||
&self
|
&self
|
||||||
.tor_keys
|
.tor_keys
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tor_key| tor_key.public().get_onion_address())
|
.map(|tor_key| tor_key.onion_address())
|
||||||
.collect(),
|
.collect(),
|
||||||
)?;
|
)?;
|
||||||
db.as_private_mut().as_password_mut().ser(&self.password)?;
|
db.as_private_mut().as_password_mut().ser(&self.password)?;
|
||||||
@@ -111,8 +112,8 @@ impl AccountInfo {
|
|||||||
.as_ssh_privkey_mut()
|
.as_ssh_privkey_mut()
|
||||||
.ser(Pem::new_ref(&self.ssh_key))?;
|
.ser(Pem::new_ref(&self.ssh_key))?;
|
||||||
db.as_private_mut()
|
db.as_private_mut()
|
||||||
.as_compat_s9pk_key_mut()
|
.as_developer_key_mut()
|
||||||
.ser(Pem::new_ref(&self.compat_s9pk_key))?;
|
.ser(Pem::new_ref(&self.developer_key))?;
|
||||||
let key_store = db.as_private_mut().as_key_store_mut();
|
let key_store = db.as_private_mut().as_key_store_mut();
|
||||||
for tor_key in &self.tor_keys {
|
for tor_key in &self.tor_keys {
|
||||||
key_store.as_onion_mut().insert_key(tor_key)?;
|
key_store.as_onion_mut().insert_key(tor_key)?;
|
||||||
@@ -131,4 +132,17 @@ impl AccountInfo {
|
|||||||
self.password = hash_password(password)?;
|
self.password = hash_password(password)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hostnames(&self) -> impl IntoIterator<Item = InternedString> + Send + '_ {
|
||||||
|
[
|
||||||
|
self.hostname.no_dot_host_name(),
|
||||||
|
self.hostname.local_domain_name(),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.chain(
|
||||||
|
self.tor_keys
|
||||||
|
.iter()
|
||||||
|
.map(|k| InternedString::from_display(&k.onion_address())),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ActionInput {
|
pub struct ActionInput {
|
||||||
|
#[serde(default)]
|
||||||
|
pub event_id: Guid,
|
||||||
#[ts(type = "Record<string, unknown>")]
|
#[ts(type = "Record<string, unknown>")]
|
||||||
pub spec: Value,
|
pub spec: Value,
|
||||||
#[ts(type = "Record<string, unknown> | null")]
|
#[ts(type = "Record<string, unknown> | null")]
|
||||||
@@ -270,6 +272,7 @@ pub fn display_action_result<T: Serialize>(
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RunActionParams {
|
pub struct RunActionParams {
|
||||||
pub package_id: PackageId,
|
pub package_id: PackageId,
|
||||||
|
pub event_id: Option<Guid>,
|
||||||
pub action_id: ActionId,
|
pub action_id: ActionId,
|
||||||
#[ts(optional, type = "any")]
|
#[ts(optional, type = "any")]
|
||||||
pub input: Option<Value>,
|
pub input: Option<Value>,
|
||||||
@@ -278,6 +281,7 @@ pub struct RunActionParams {
|
|||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct CliRunActionParams {
|
struct CliRunActionParams {
|
||||||
pub package_id: PackageId,
|
pub package_id: PackageId,
|
||||||
|
pub event_id: Option<Guid>,
|
||||||
pub action_id: ActionId,
|
pub action_id: ActionId,
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub input: StdinDeserializable<Option<Value>>,
|
pub input: StdinDeserializable<Option<Value>>,
|
||||||
@@ -286,12 +290,14 @@ impl From<CliRunActionParams> for RunActionParams {
|
|||||||
fn from(
|
fn from(
|
||||||
CliRunActionParams {
|
CliRunActionParams {
|
||||||
package_id,
|
package_id,
|
||||||
|
event_id,
|
||||||
action_id,
|
action_id,
|
||||||
input,
|
input,
|
||||||
}: CliRunActionParams,
|
}: CliRunActionParams,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
package_id,
|
package_id,
|
||||||
|
event_id,
|
||||||
action_id,
|
action_id,
|
||||||
input: input.0,
|
input: input.0,
|
||||||
}
|
}
|
||||||
@@ -331,6 +337,7 @@ pub async fn run_action(
|
|||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
RunActionParams {
|
RunActionParams {
|
||||||
package_id,
|
package_id,
|
||||||
|
event_id,
|
||||||
action_id,
|
action_id,
|
||||||
input,
|
input,
|
||||||
}: RunActionParams,
|
}: RunActionParams,
|
||||||
@@ -340,7 +347,11 @@ pub async fn run_action(
|
|||||||
.await
|
.await
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.or_not_found(lazy_format!("Manager for {}", package_id))?
|
.or_not_found(lazy_format!("Manager for {}", package_id))?
|
||||||
.run_action(Guid::new(), action_id, input.unwrap_or_default())
|
.run_action(
|
||||||
|
event_id.unwrap_or_default(),
|
||||||
|
action_id,
|
||||||
|
input.unwrap_or_default(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map(|res| res.map(ActionResult::upcast))
|
.map(|res| res.map(ActionResult::upcast))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,29 +3,27 @@ use std::collections::BTreeMap;
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use imbl_value::{json, InternedString};
|
use imbl_value::{InternedString, json};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{from_fn_async, Context, HandlerArgs, HandlerExt, ParentHandler};
|
use rpc_toolkit::{CallRemote, Context, HandlerArgs, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
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::middleware::auth::{
|
use crate::middleware::auth::{
|
||||||
AsLogoutSessionId, HasLoggedOutSessions, HashSessionToken, LoginRes,
|
AsLogoutSessionId, AuthContext, HasLoggedOutSessions, HashSessionToken, LoginRes,
|
||||||
};
|
};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::crypto::EncryptedWire;
|
use crate::util::crypto::EncryptedWire;
|
||||||
use crate::util::io::create_file_mod;
|
use crate::util::io::create_file_mod;
|
||||||
use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
|
use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable};
|
||||||
use crate::{ensure_code, Error, ResultExt};
|
use crate::{Error, ResultExt, ensure_code};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, TS)]
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, TS)]
|
||||||
#[ts(as = "BTreeMap::<String, Session>")]
|
|
||||||
pub struct Sessions(pub BTreeMap<InternedString, Session>);
|
pub struct Sessions(pub BTreeMap<InternedString, Session>);
|
||||||
impl Sessions {
|
impl Sessions {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -112,31 +110,34 @@ impl std::str::FromStr for PasswordType {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn auth<C: Context>() -> ParentHandler<C> {
|
pub fn auth<C: Context, AC: AuthContext>() -> ParentHandler<C>
|
||||||
|
where
|
||||||
|
CliContext: CallRemote<AC>,
|
||||||
|
{
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"login",
|
"login",
|
||||||
from_fn_async(login_impl)
|
from_fn_async(login_impl::<AC>)
|
||||||
.with_metadata("login", Value::Bool(true))
|
.with_metadata("login", Value::Bool(true))
|
||||||
.no_cli(),
|
.no_cli(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"login",
|
"login",
|
||||||
from_fn_async(cli_login)
|
from_fn_async(cli_login::<AC>)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Log in to StartOS server"),
|
.with_about("Log in a new auth session"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"logout",
|
"logout",
|
||||||
from_fn_async(logout)
|
from_fn_async(logout::<AC>)
|
||||||
.with_metadata("get_session", Value::Bool(true))
|
.with_metadata("get_session", Value::Bool(true))
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Log out of StartOS server")
|
.with_about("Log out of current auth session")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"session",
|
"session",
|
||||||
session::<C>().with_about("List or kill StartOS sessions"),
|
session::<C, AC>().with_about("List or kill auth sessions"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"reset-password",
|
"reset-password",
|
||||||
@@ -146,7 +147,7 @@ pub fn auth<C: Context>() -> ParentHandler<C> {
|
|||||||
"reset-password",
|
"reset-password",
|
||||||
from_fn_async(cli_reset_password)
|
from_fn_async(cli_reset_password)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Reset StartOS password"),
|
.with_about("Reset password"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"get-pubkey",
|
"get-pubkey",
|
||||||
@@ -172,17 +173,20 @@ fn gen_pwd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn cli_login(
|
async fn cli_login<C: AuthContext>(
|
||||||
HandlerArgs {
|
HandlerArgs {
|
||||||
context: ctx,
|
context: ctx,
|
||||||
parent_method,
|
parent_method,
|
||||||
method,
|
method,
|
||||||
..
|
..
|
||||||
}: HandlerArgs<CliContext>,
|
}: HandlerArgs<CliContext>,
|
||||||
) -> Result<(), RpcError> {
|
) -> Result<(), RpcError>
|
||||||
|
where
|
||||||
|
CliContext: CallRemote<C>,
|
||||||
|
{
|
||||||
let password = rpassword::prompt_password("Password: ")?;
|
let password = rpassword::prompt_password("Password: ")?;
|
||||||
|
|
||||||
ctx.call_remote::<RpcContext>(
|
ctx.call_remote::<C>(
|
||||||
&parent_method.into_iter().chain(method).join("."),
|
&parent_method.into_iter().chain(method).join("."),
|
||||||
json!({
|
json!({
|
||||||
"password": password,
|
"password": password,
|
||||||
@@ -210,17 +214,11 @@ pub fn check_password(hash: &str, password: &str) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_password_against_db(db: &DatabaseModel, password: &str) -> Result<(), Error> {
|
|
||||||
let pw_hash = db.as_private().as_password().de()?;
|
|
||||||
check_password(&pw_hash, password)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, TS)]
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct LoginParams {
|
pub struct LoginParams {
|
||||||
password: Option<PasswordType>,
|
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>,
|
||||||
@@ -229,20 +227,18 @@ pub struct LoginParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn login_impl(
|
pub async fn login_impl<C: AuthContext>(
|
||||||
ctx: RpcContext,
|
ctx: C,
|
||||||
LoginParams {
|
LoginParams {
|
||||||
password,
|
password,
|
||||||
user_agent,
|
user_agent,
|
||||||
ephemeral,
|
ephemeral,
|
||||||
}: LoginParams,
|
}: LoginParams,
|
||||||
) -> Result<LoginRes, Error> {
|
) -> Result<LoginRes, Error> {
|
||||||
let password = password.unwrap_or_default().decrypt(&ctx)?;
|
|
||||||
|
|
||||||
let tok = if ephemeral {
|
let tok = if ephemeral {
|
||||||
check_password_against_db(&ctx.db.peek().await, &password)?;
|
C::check_password(&ctx.db().peek().await, &password)?;
|
||||||
let hash_token = HashSessionToken::new();
|
let hash_token = HashSessionToken::new();
|
||||||
ctx.ephemeral_sessions.mutate(|s| {
|
ctx.ephemeral_sessions().mutate(|s| {
|
||||||
s.0.insert(
|
s.0.insert(
|
||||||
hash_token.hashed().clone(),
|
hash_token.hashed().clone(),
|
||||||
Session {
|
Session {
|
||||||
@@ -254,11 +250,11 @@ pub async fn login_impl(
|
|||||||
});
|
});
|
||||||
Ok(hash_token.to_login_res())
|
Ok(hash_token.to_login_res())
|
||||||
} else {
|
} else {
|
||||||
ctx.db
|
ctx.db()
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
check_password_against_db(db, &password)?;
|
C::check_password(db, &password)?;
|
||||||
let hash_token = HashSessionToken::new();
|
let hash_token = HashSessionToken::new();
|
||||||
db.as_private_mut().as_sessions_mut().insert(
|
C::access_sessions(db).insert(
|
||||||
hash_token.hashed(),
|
hash_token.hashed(),
|
||||||
&Session {
|
&Session {
|
||||||
logged_in: Utc::now(),
|
logged_in: Utc::now(),
|
||||||
@@ -273,12 +269,7 @@ pub async fn login_impl(
|
|||||||
.result
|
.result
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
if tokio::fs::metadata("/media/startos/config/overlay/etc/shadow")
|
ctx.post_login_hook(&password).await?;
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
write_shadow(&password).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(tok)
|
Ok(tok)
|
||||||
}
|
}
|
||||||
@@ -292,8 +283,8 @@ pub struct LogoutParams {
|
|||||||
session: InternedString,
|
session: InternedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn logout(
|
pub async fn logout<C: AuthContext>(
|
||||||
ctx: RpcContext,
|
ctx: C,
|
||||||
LogoutParams { session }: LogoutParams,
|
LogoutParams { session }: LogoutParams,
|
||||||
) -> Result<Option<HasLoggedOutSessions>, Error> {
|
) -> Result<Option<HasLoggedOutSessions>, Error> {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
@@ -321,22 +312,25 @@ pub struct SessionList {
|
|||||||
sessions: Sessions,
|
sessions: Sessions,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session<C: Context>() -> ParentHandler<C> {
|
pub fn session<C: Context, AC: AuthContext>() -> ParentHandler<C>
|
||||||
|
where
|
||||||
|
CliContext: CallRemote<AC>,
|
||||||
|
{
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"list",
|
"list",
|
||||||
from_fn_async(list)
|
from_fn_async(list::<AC>)
|
||||||
.with_metadata("get_session", Value::Bool(true))
|
.with_metadata("get_session", Value::Bool(true))
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| display_sessions(handle.params, result))
|
.with_custom_display_fn(|handle, result| display_sessions(handle.params, result))
|
||||||
.with_about("Display all server sessions")
|
.with_about("Display all auth sessions")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"kill",
|
"kill",
|
||||||
from_fn_async(kill)
|
from_fn_async(kill::<AC>)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Terminate existing server session(s)")
|
.with_about("Terminate existing auth session(s)")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -385,12 +379,12 @@ pub struct ListParams {
|
|||||||
|
|
||||||
// #[command(display(display_sessions))]
|
// #[command(display(display_sessions))]
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn list(
|
pub async fn list<C: AuthContext>(
|
||||||
ctx: RpcContext,
|
ctx: C,
|
||||||
ListParams { session, .. }: ListParams,
|
ListParams { session, .. }: ListParams,
|
||||||
) -> Result<SessionList, Error> {
|
) -> Result<SessionList, Error> {
|
||||||
let mut sessions = ctx.db.peek().await.into_private().into_sessions().de()?;
|
let mut sessions = C::access_sessions(&mut ctx.db().peek().await).de()?;
|
||||||
ctx.ephemeral_sessions.peek(|s| {
|
ctx.ephemeral_sessions().peek(|s| {
|
||||||
sessions
|
sessions
|
||||||
.0
|
.0
|
||||||
.extend(s.0.iter().map(|(k, v)| (k.clone(), v.clone())))
|
.extend(s.0.iter().map(|(k, v)| (k.clone(), v.clone())))
|
||||||
@@ -424,7 +418,7 @@ pub struct KillParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn kill(ctx: RpcContext, KillParams { ids }: KillParams) -> Result<(), Error> {
|
pub async fn kill<C: AuthContext>(ctx: C, KillParams { ids }: KillParams) -> Result<(), Error> {
|
||||||
HasLoggedOutSessions::new(ids.into_iter().map(KillSessionId::new), &ctx).await?;
|
HasLoggedOutSessions::new(ids.into_iter().map(KillSessionId::new), &ctx).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ use tokio::io::AsyncWriteExt;
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use super::target::{BackupTargetId, PackageBackupInfo};
|
|
||||||
use super::PackageBackupReport;
|
use super::PackageBackupReport;
|
||||||
use crate::auth::check_password_against_db;
|
use super::target::{BackupTargetId, PackageBackupInfo};
|
||||||
use crate::backup::os::OsBackup;
|
use crate::backup::os::OsBackup;
|
||||||
use crate::backup::{BackupReport, ServerBackupReport};
|
use crate::backup::{BackupReport, ServerBackupReport};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
@@ -24,7 +23,8 @@ use crate::db::model::{Database, DatabaseModel};
|
|||||||
use crate::disk::mount::backup::BackupMountGuard;
|
use crate::disk::mount::backup::BackupMountGuard;
|
||||||
use crate::disk::mount::filesystem::ReadWrite;
|
use crate::disk::mount::filesystem::ReadWrite;
|
||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
use crate::notifications::{notify, NotificationLevel};
|
use crate::middleware::auth::AuthContext;
|
||||||
|
use crate::notifications::{NotificationLevel, notify};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::io::dir_copy;
|
use crate::util::io::dir_copy;
|
||||||
use crate::util::serde::IoFormat;
|
use crate::util::serde::IoFormat;
|
||||||
@@ -170,7 +170,7 @@ pub async fn backup_all(
|
|||||||
let ((fs, package_ids, server_id), status_guard) = (
|
let ((fs, package_ids, server_id), status_guard) = (
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
check_password_against_db(db, &password)?;
|
RpcContext::check_password(db, &password)?;
|
||||||
let fs = target_id.load(db)?;
|
let fs = target_id.load(db)?;
|
||||||
let package_ids = if let Some(ids) = package_ids {
|
let package_ids = if let Some(ids) = package_ids {
|
||||||
ids.into_iter().collect()
|
ids.into_iter().collect()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use models::{HostId, PackageId};
|
use models::{HostId, PackageId};
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
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 crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ use openssl::x509::X509;
|
|||||||
use patch_db::Value;
|
use patch_db::Value;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ssh_key::private::Ed25519Keypair;
|
use ssh_key::private::Ed25519Keypair;
|
||||||
use torut::onion::TorSecretKeyV3;
|
|
||||||
|
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::hostname::{generate_hostname, generate_id, Hostname};
|
use crate::hostname::{Hostname, generate_hostname, generate_id};
|
||||||
|
use crate::net::tor::TorSecretKey;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::crypto::ed25519_expand_key;
|
use crate::util::crypto::ed25519_expand_key;
|
||||||
use crate::util::serde::{Base32, Base64, Pem};
|
use crate::util::serde::{Base32, Base64, Pem};
|
||||||
@@ -36,7 +36,7 @@ impl<'de> Deserialize<'de> for OsBackup {
|
|||||||
v => {
|
v => {
|
||||||
return Err(serde::de::Error::custom(&format!(
|
return Err(serde::de::Error::custom(&format!(
|
||||||
"Unknown backup version {v}"
|
"Unknown backup version {v}"
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -85,8 +85,11 @@ impl OsBackupV0 {
|
|||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
ssh_key::Algorithm::Ed25519,
|
ssh_key::Algorithm::Ed25519,
|
||||||
)?,
|
)?,
|
||||||
tor_keys: vec![TorSecretKeyV3::from(self.tor_key.0)],
|
tor_keys: TorSecretKey::from_bytes(self.tor_key.0)
|
||||||
compat_s9pk_key: ed25519_dalek::SigningKey::generate(
|
.ok()
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
developer_key: ed25519_dalek::SigningKey::generate(
|
||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -116,8 +119,11 @@ impl OsBackupV1 {
|
|||||||
root_ca_key: self.root_ca_key.0,
|
root_ca_key: self.root_ca_key.0,
|
||||||
root_ca_cert: self.root_ca_cert.0,
|
root_ca_cert: self.root_ca_cert.0,
|
||||||
ssh_key: ssh_key::PrivateKey::from(Ed25519Keypair::from_seed(&self.net_key.0)),
|
ssh_key: ssh_key::PrivateKey::from(Ed25519Keypair::from_seed(&self.net_key.0)),
|
||||||
tor_keys: vec![TorSecretKeyV3::from(ed25519_expand_key(&self.net_key.0))],
|
tor_keys: TorSecretKey::from_bytes(ed25519_expand_key(&self.net_key.0))
|
||||||
compat_s9pk_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key),
|
.ok()
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
developer_key: ed25519_dalek::SigningKey::from_bytes(&self.net_key),
|
||||||
},
|
},
|
||||||
ui: self.ui,
|
ui: self.ui,
|
||||||
}
|
}
|
||||||
@@ -134,7 +140,7 @@ struct OsBackupV2 {
|
|||||||
root_ca_key: Pem<PKey<Private>>, // PEM Encoded OpenSSL Key
|
root_ca_key: Pem<PKey<Private>>, // PEM Encoded OpenSSL Key
|
||||||
root_ca_cert: Pem<X509>, // PEM Encoded OpenSSL X509 Certificate
|
root_ca_cert: Pem<X509>, // PEM Encoded OpenSSL X509 Certificate
|
||||||
ssh_key: Pem<ssh_key::PrivateKey>, // PEM Encoded OpenSSH Key
|
ssh_key: Pem<ssh_key::PrivateKey>, // PEM Encoded OpenSSH Key
|
||||||
tor_keys: Vec<TorSecretKeyV3>, // Base64 Encoded Ed25519 Expanded Secret Key
|
tor_keys: Vec<TorSecretKey>, // Base64 Encoded Ed25519 Expanded Secret Key
|
||||||
compat_s9pk_key: Pem<ed25519_dalek::SigningKey>, // PEM Encoded ED25519 Key
|
compat_s9pk_key: Pem<ed25519_dalek::SigningKey>, // PEM Encoded ED25519 Key
|
||||||
ui: Value, // JSON Value
|
ui: Value, // JSON Value
|
||||||
}
|
}
|
||||||
@@ -149,7 +155,7 @@ impl OsBackupV2 {
|
|||||||
root_ca_cert: self.root_ca_cert.0,
|
root_ca_cert: self.root_ca_cert.0,
|
||||||
ssh_key: self.ssh_key.0,
|
ssh_key: self.ssh_key.0,
|
||||||
tor_keys: self.tor_keys,
|
tor_keys: self.tor_keys,
|
||||||
compat_s9pk_key: self.compat_s9pk_key.0,
|
developer_key: self.compat_s9pk_key.0,
|
||||||
},
|
},
|
||||||
ui: self.ui,
|
ui: self.ui,
|
||||||
}
|
}
|
||||||
@@ -162,7 +168,7 @@ impl OsBackupV2 {
|
|||||||
root_ca_cert: Pem(backup.account.root_ca_cert.clone()),
|
root_ca_cert: Pem(backup.account.root_ca_cert.clone()),
|
||||||
ssh_key: Pem(backup.account.ssh_key.clone()),
|
ssh_key: Pem(backup.account.ssh_key.clone()),
|
||||||
tor_keys: backup.account.tor_keys.clone(),
|
tor_keys: backup.account.tor_keys.clone(),
|
||||||
compat_s9pk_key: Pem(backup.account.compat_s9pk_key.clone()),
|
compat_s9pk_key: Pem(backup.account.developer_key.clone()),
|
||||||
ui: backup.ui.clone(),
|
ui: backup.ui.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::{stream, StreamExt};
|
use futures::{StreamExt, stream};
|
||||||
use models::PackageId;
|
use models::PackageId;
|
||||||
use patch_db::json_ptr::ROOT;
|
use patch_db::json_ptr::ROOT;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -11,6 +11,7 @@ use tracing::instrument;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use super::target::BackupTargetId;
|
use super::target::BackupTargetId;
|
||||||
|
use crate::PLATFORM;
|
||||||
use crate::backup::os::OsBackup;
|
use crate::backup::os::OsBackup;
|
||||||
use crate::context::setup::SetupResult;
|
use crate::context::setup::SetupResult;
|
||||||
use crate::context::{RpcContext, SetupContext};
|
use crate::context::{RpcContext, SetupContext};
|
||||||
@@ -26,7 +27,6 @@ use crate::service::service_map::DownloadInstallFuture;
|
|||||||
use crate::setup::SetupExecuteProgress;
|
use crate::setup::SetupExecuteProgress;
|
||||||
use crate::system::sync_kiosk;
|
use crate::system::sync_kiosk;
|
||||||
use crate::util::serde::IoFormat;
|
use crate::util::serde::IoFormat;
|
||||||
use crate::PLATFORM;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ use std::path::{Path, PathBuf};
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
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 ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use super::{BackupTarget, BackupTargetId};
|
use super::{BackupTarget, BackupTargetId};
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::DatabaseModel;
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::disk::mount::filesystem::cifs::Cifs;
|
|
||||||
use crate::disk::mount::filesystem::ReadOnly;
|
use crate::disk::mount::filesystem::ReadOnly;
|
||||||
|
use crate::disk::mount::filesystem::cifs::Cifs;
|
||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
use crate::disk::util::{recovery_info, StartOsRecoveryInfo};
|
use crate::disk::util::{StartOsRecoveryInfo, recovery_info};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::KeyVal;
|
use crate::util::serde::KeyVal;
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ use std::collections::BTreeMap;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use clap::builder::ValueParserFactory;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use clap::builder::ValueParserFactory;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use digest::generic_array::GenericArray;
|
|
||||||
use digest::OutputSizeUser;
|
use digest::OutputSizeUser;
|
||||||
|
use digest::generic_array::GenericArray;
|
||||||
use exver::Version;
|
use exver::Version;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use models::{FromStrParser, PackageId};
|
use models::{FromStrParser, PackageId};
|
||||||
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 sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -27,10 +27,10 @@ use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite};
|
|||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
use crate::disk::util::PartitionInfo;
|
use crate::disk::util::PartitionInfo;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::{
|
|
||||||
deserialize_from_str, display_serializable, serialize_display, HandlerExtSerde, WithIoFormat,
|
|
||||||
};
|
|
||||||
use crate::util::VersionString;
|
use crate::util::VersionString;
|
||||||
|
use crate::util::serde::{
|
||||||
|
HandlerExtSerde, WithIoFormat, deserialize_from_str, display_serializable, serialize_display,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod cifs;
|
pub mod cifs;
|
||||||
|
|
||||||
|
|||||||
@@ -2,41 +2,64 @@ use std::collections::VecDeque;
|
|||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[cfg(feature = "container-runtime")]
|
#[cfg(feature = "cli-container")]
|
||||||
pub mod container_cli;
|
pub mod container_cli;
|
||||||
pub mod deprecated;
|
pub mod deprecated;
|
||||||
#[cfg(feature = "registry")]
|
#[cfg(any(feature = "registry", feature = "cli-registry"))]
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
pub mod start_cli;
|
pub mod start_cli;
|
||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "startd")]
|
||||||
pub mod start_init;
|
pub mod start_init;
|
||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "startd")]
|
||||||
pub mod startd;
|
pub mod startd;
|
||||||
|
#[cfg(any(feature = "tunnel", feature = "cli-tunnel"))]
|
||||||
|
pub mod tunnel;
|
||||||
|
|
||||||
fn select_executable(name: &str) -> Option<fn(VecDeque<OsString>)> {
|
fn select_executable(name: &str) -> Option<fn(VecDeque<OsString>)> {
|
||||||
match name {
|
match name {
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "startd")]
|
||||||
"start-cli" => Some(start_cli::main),
|
|
||||||
#[cfg(feature = "container-runtime")]
|
|
||||||
"start-cli" => Some(container_cli::main),
|
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
"startd" => Some(startd::main),
|
"startd" => Some(startd::main),
|
||||||
#[cfg(feature = "registry")]
|
#[cfg(feature = "startd")]
|
||||||
"registry" => Some(registry::main),
|
|
||||||
"embassy-cli" => Some(|_| deprecated::renamed("embassy-cli", "start-cli")),
|
|
||||||
"embassy-sdk" => Some(|_| deprecated::renamed("embassy-sdk", "start-sdk")),
|
|
||||||
"embassyd" => Some(|_| deprecated::renamed("embassyd", "startd")),
|
"embassyd" => Some(|_| deprecated::renamed("embassyd", "startd")),
|
||||||
|
#[cfg(feature = "startd")]
|
||||||
"embassy-init" => Some(|_| deprecated::removed("embassy-init")),
|
"embassy-init" => Some(|_| deprecated::removed("embassy-init")),
|
||||||
"contents" => Some(|_| {
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli-startd")]
|
||||||
println!("start-cli");
|
"start-cli" => Some(start_cli::main),
|
||||||
#[cfg(feature = "container-runtime")]
|
#[cfg(feature = "cli-startd")]
|
||||||
println!("start-cli (container)");
|
"embassy-cli" => Some(|_| deprecated::renamed("embassy-cli", "start-cli")),
|
||||||
#[cfg(feature = "daemon")]
|
#[cfg(feature = "cli-startd")]
|
||||||
println!("startd");
|
"embassy-sdk" => Some(|_| deprecated::removed("embassy-sdk")),
|
||||||
|
|
||||||
|
#[cfg(feature = "cli-container")]
|
||||||
|
"start-container" => Some(container_cli::main),
|
||||||
|
|
||||||
#[cfg(feature = "registry")]
|
#[cfg(feature = "registry")]
|
||||||
println!("registry");
|
"start-registryd" => Some(registry::main),
|
||||||
|
#[cfg(feature = "cli-registry")]
|
||||||
|
"start-registry" => Some(registry::cli),
|
||||||
|
|
||||||
|
#[cfg(feature = "tunnel")]
|
||||||
|
"start-tunneld" => Some(tunnel::main),
|
||||||
|
#[cfg(feature = "cli-tunnel")]
|
||||||
|
"start-tunnel" => Some(tunnel::cli),
|
||||||
|
|
||||||
|
"contents" => Some(|_| {
|
||||||
|
#[cfg(feature = "startd")]
|
||||||
|
println!("startd");
|
||||||
|
#[cfg(feature = "cli-startd")]
|
||||||
|
println!("start-cli");
|
||||||
|
#[cfg(feature = "cli-container")]
|
||||||
|
println!("start-container");
|
||||||
|
#[cfg(feature = "registry")]
|
||||||
|
println!("start-registryd");
|
||||||
|
#[cfg(feature = "cli-registry")]
|
||||||
|
println!("start-registry");
|
||||||
|
#[cfg(feature = "tunnel")]
|
||||||
|
println!("start-tunneld");
|
||||||
|
#[cfg(feature = "cli-tunnel")]
|
||||||
|
println!("start-tunnel");
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ use std::ffi::OsString;
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
use rpc_toolkit::CliApp;
|
||||||
use tokio::signal::unix::signal;
|
use tokio::signal::unix::signal;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::context::CliContext;
|
||||||
|
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};
|
||||||
@@ -85,3 +88,30 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cli(args: impl IntoIterator<Item = OsString>) {
|
||||||
|
LOGGER.enable();
|
||||||
|
|
||||||
|
if let Err(e) = CliApp::new(
|
||||||
|
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||||
|
crate::registry::registry_api(),
|
||||||
|
)
|
||||||
|
.run(args)
|
||||||
|
{
|
||||||
|
match e.data {
|
||||||
|
Some(serde_json::Value::String(s)) => eprintln!("{}: {}", e.message, s),
|
||||||
|
Some(serde_json::Value::Object(o)) => {
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("details") {
|
||||||
|
eprintln!("{}: {}", e.message, s);
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("debug") {
|
||||||
|
tracing::debug!("{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(a) => eprintln!("{}: {}", e.message, a),
|
||||||
|
None => eprintln!("{}", e.message),
|
||||||
|
}
|
||||||
|
|
||||||
|
std::process::exit(e.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use std::ffi::OsString;
|
|||||||
use rpc_toolkit::CliApp;
|
use rpc_toolkit::CliApp;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::context::config::ClientConfig;
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
|
use crate::context::config::ClientConfig;
|
||||||
use crate::util::logger::LOGGER;
|
use crate::util::logger::LOGGER;
|
||||||
use crate::version::{Current, VersionT};
|
use crate::version::{Current, VersionT};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -7,9 +6,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, InstallContext, RpcContext, SetupContext};
|
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||||
|
use crate::disk::REPAIR_DISK_PATH;
|
||||||
use crate::disk::fsck::RepairStrategy;
|
use crate::disk::fsck::RepairStrategy;
|
||||||
use crate::disk::main::DEFAULT_PASSWORD;
|
use crate::disk::main::DEFAULT_PASSWORD;
|
||||||
use crate::disk::REPAIR_DISK_PATH;
|
|
||||||
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::web_server::{UpgradableListener, WebServer};
|
||||||
@@ -48,7 +47,7 @@ async fn setup_or_init(
|
|||||||
update_phase.complete();
|
update_phase.complete();
|
||||||
reboot_phase.start();
|
reboot_phase.start();
|
||||||
return Ok(Err(Shutdown {
|
return Ok(Err(Shutdown {
|
||||||
export_args: None,
|
disk_guid: None,
|
||||||
restart: true,
|
restart: true,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -103,7 +102,7 @@ async fn setup_or_init(
|
|||||||
.expect("context dropped");
|
.expect("context dropped");
|
||||||
|
|
||||||
return Ok(Err(Shutdown {
|
return Ok(Err(Shutdown {
|
||||||
export_args: None,
|
disk_guid: None,
|
||||||
restart: true,
|
restart: true,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -117,7 +116,9 @@ async fn setup_or_init(
|
|||||||
server.serve_setup(ctx.clone());
|
server.serve_setup(ctx.clone());
|
||||||
|
|
||||||
let mut shutdown = ctx.shutdown.subscribe();
|
let mut shutdown = ctx.shutdown.subscribe();
|
||||||
shutdown.recv().await.expect("context dropped");
|
if let Some(shutdown) = shutdown.recv().await.expect("context dropped") {
|
||||||
|
return Ok(Err(shutdown));
|
||||||
|
}
|
||||||
|
|
||||||
tokio::task::yield_now().await;
|
tokio::task::yield_now().await;
|
||||||
if let Err(e) = Command::new("killall")
|
if let Err(e) = Command::new("killall")
|
||||||
@@ -136,7 +137,7 @@ async fn setup_or_init(
|
|||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("Setup mode exited before setup completed"),
|
eyre!("Setup mode exited before setup completed"),
|
||||||
ErrorKind::Unknown,
|
ErrorKind::Unknown,
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
@@ -183,7 +184,7 @@ async fn setup_or_init(
|
|||||||
let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
|
let mut reboot_phase = handle.add_phase("Rebooting".into(), Some(1));
|
||||||
reboot_phase.start();
|
reboot_phase.start();
|
||||||
return Ok(Err(Shutdown {
|
return Ok(Err(Shutdown {
|
||||||
export_args: Some((disk_guid, Path::new(DATA_DIR).to_owned())),
|
disk_guid: Some(disk_guid),
|
||||||
restart: true,
|
restart: true,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ 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::network_interface::SelfContainedNetworkInterfaceListener;
|
use crate::net::gateway::SelfContainedNetworkInterfaceListener;
|
||||||
use crate::net::web_server::{Acceptor, UpgradableListener, WebServer};
|
use crate::net::web_server::{Acceptor, UpgradableListener, WebServer};
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
use crate::system::launch_metrics_task;
|
use crate::system::launch_metrics_task;
|
||||||
@@ -144,7 +144,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
|
|||||||
|
|
||||||
let res = {
|
let res = {
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.worker_threads(max(4, num_cpus::get()))
|
.worker_threads(max(1, num_cpus::get()))
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.expect("failed to initialize runtime");
|
.expect("failed to initialize runtime");
|
||||||
|
|||||||
117
core/startos/src/bins/tunnel.rs
Normal file
117
core/startos/src/bins/tunnel.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use futures::FutureExt;
|
||||||
|
use rpc_toolkit::CliApp;
|
||||||
|
use tokio::signal::unix::signal;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::context::CliContext;
|
||||||
|
use crate::context::config::ClientConfig;
|
||||||
|
use crate::net::web_server::{Acceptor, WebServer};
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::tunnel::context::{TunnelConfig, TunnelContext};
|
||||||
|
use crate::util::logger::LOGGER;
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
async fn inner_main(config: &TunnelConfig) -> Result<(), Error> {
|
||||||
|
let server = async {
|
||||||
|
let ctx = TunnelContext::init(config).await?;
|
||||||
|
let mut server = WebServer::new(Acceptor::bind([ctx.listen]).await?);
|
||||||
|
server.serve_tunnel(ctx.clone());
|
||||||
|
|
||||||
|
let mut shutdown_recv = ctx.shutdown.subscribe();
|
||||||
|
|
||||||
|
let sig_handler_ctx = ctx;
|
||||||
|
let sig_handler = tokio::spawn(async move {
|
||||||
|
use tokio::signal::unix::SignalKind;
|
||||||
|
futures::future::select_all(
|
||||||
|
[
|
||||||
|
SignalKind::interrupt(),
|
||||||
|
SignalKind::quit(),
|
||||||
|
SignalKind::terminate(),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
async move {
|
||||||
|
signal(*s)
|
||||||
|
.unwrap_or_else(|_| panic!("register {:?} handler", s))
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
.boxed()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
sig_handler_ctx
|
||||||
|
.shutdown
|
||||||
|
.send(())
|
||||||
|
.map_err(|_| ())
|
||||||
|
.expect("send shutdown signal");
|
||||||
|
});
|
||||||
|
|
||||||
|
shutdown_recv
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
.with_kind(crate::ErrorKind::Unknown)?;
|
||||||
|
|
||||||
|
sig_handler.abort();
|
||||||
|
|
||||||
|
Ok::<_, Error>(server)
|
||||||
|
}
|
||||||
|
.await?;
|
||||||
|
server.shutdown().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(args: impl IntoIterator<Item = OsString>) {
|
||||||
|
LOGGER.enable();
|
||||||
|
|
||||||
|
let config = TunnelConfig::parse_from(args).load().unwrap();
|
||||||
|
|
||||||
|
let res = {
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("failed to initialize runtime");
|
||||||
|
rt.block_on(inner_main(&config))
|
||||||
|
};
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{}", e.source);
|
||||||
|
tracing::debug!("{:?}", e.source);
|
||||||
|
drop(e.source);
|
||||||
|
std::process::exit(e.kind as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cli(args: impl IntoIterator<Item = OsString>) {
|
||||||
|
LOGGER.enable();
|
||||||
|
|
||||||
|
if let Err(e) = CliApp::new(
|
||||||
|
|cfg: ClientConfig| Ok(CliContext::init(cfg.load()?)?),
|
||||||
|
crate::tunnel::api::tunnel_api(),
|
||||||
|
)
|
||||||
|
.run(args)
|
||||||
|
{
|
||||||
|
match e.data {
|
||||||
|
Some(serde_json::Value::String(s)) => eprintln!("{}: {}", e.message, s),
|
||||||
|
Some(serde_json::Value::Object(o)) => {
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("details") {
|
||||||
|
eprintln!("{}: {}", e.message, s);
|
||||||
|
if let Some(serde_json::Value::String(s)) = o.get("debug") {
|
||||||
|
tracing::debug!("{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(a) => eprintln!("{}: {}", e.message, a),
|
||||||
|
None => eprintln!("{}", e.message),
|
||||||
|
}
|
||||||
|
|
||||||
|
std::process::exit(e.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +1,32 @@
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cookie_store::{CookieStore, RawCookie};
|
use cookie::{Cookie, Expiration, SameSite};
|
||||||
|
use cookie_store::CookieStore;
|
||||||
|
use imbl_value::InternedString;
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use reqwest::Proxy;
|
use reqwest::Proxy;
|
||||||
use reqwest_cookie_store::CookieStoreMutex;
|
use reqwest_cookie_store::CookieStoreMutex;
|
||||||
use rpc_toolkit::reqwest::{Client, Url};
|
use rpc_toolkit::reqwest::{Client, Url};
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{call_remote_http, CallRemote, Context, Empty};
|
use rpc_toolkit::{CallRemote, Context, Empty};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::setup::CURRENT_SECRET;
|
use super::setup::CURRENT_SECRET;
|
||||||
use crate::context::config::{local_config_path, ClientConfig};
|
use crate::context::config::{ClientConfig, local_config_path};
|
||||||
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
use crate::context::{DiagnosticContext, InitContext, InstallContext, RpcContext, SetupContext};
|
||||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
use crate::developer::{OS_DEVELOPER_KEY_PATH, default_developer_key_path};
|
||||||
|
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;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CliContextSeed {
|
pub struct CliContextSeed {
|
||||||
@@ -29,6 +34,10 @@ pub struct CliContextSeed {
|
|||||||
pub base_url: Url,
|
pub base_url: Url,
|
||||||
pub rpc_url: Url,
|
pub rpc_url: Url,
|
||||||
pub registry_url: Option<Url>,
|
pub registry_url: Option<Url>,
|
||||||
|
pub registry_hostname: Option<InternedString>,
|
||||||
|
pub registry_listen: Option<SocketAddr>,
|
||||||
|
pub tunnel_addr: Option<SocketAddr>,
|
||||||
|
pub tunnel_listen: Option<SocketAddr>,
|
||||||
pub client: Client,
|
pub client: Client,
|
||||||
pub cookie_store: Arc<CookieStoreMutex>,
|
pub cookie_store: Arc<CookieStoreMutex>,
|
||||||
pub cookie_path: PathBuf,
|
pub cookie_path: PathBuf,
|
||||||
@@ -55,9 +64,8 @@ impl Drop for CliContextSeed {
|
|||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut store = self.cookie_store.lock().unwrap();
|
let store = self.cookie_store.lock().unwrap();
|
||||||
store.remove("localhost", "", "local");
|
cookie_store::serde::json::save(&store, &mut *writer).unwrap();
|
||||||
store.save_json(&mut *writer).unwrap();
|
|
||||||
writer.sync_all().unwrap();
|
writer.sync_all().unwrap();
|
||||||
std::fs::rename(tmp, &self.cookie_path).unwrap();
|
std::fs::rename(tmp, &self.cookie_path).unwrap();
|
||||||
}
|
}
|
||||||
@@ -85,26 +93,14 @@ impl CliContext {
|
|||||||
.unwrap_or(Path::new("/"))
|
.unwrap_or(Path::new("/"))
|
||||||
.join(".cookies.json")
|
.join(".cookies.json")
|
||||||
});
|
});
|
||||||
let cookie_store = Arc::new(CookieStoreMutex::new({
|
let cookie_store = Arc::new(CookieStoreMutex::new(if cookie_path.exists() {
|
||||||
let mut store = if cookie_path.exists() {
|
cookie_store::serde::json::load(BufReader::new(
|
||||||
CookieStore::load_json(BufReader::new(
|
|
||||||
File::open(&cookie_path)
|
File::open(&cookie_path)
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, cookie_path.display()))?,
|
.with_ctx(|_| (ErrorKind::Filesystem, cookie_path.display()))?,
|
||||||
))
|
))
|
||||||
.map_err(|e| eyre!("{}", e))
|
.unwrap_or_default()
|
||||||
.with_kind(crate::ErrorKind::Deserialization)?
|
|
||||||
} else {
|
} else {
|
||||||
CookieStore::default()
|
CookieStore::default()
|
||||||
};
|
|
||||||
if let Ok(local) = std::fs::read_to_string(LOCAL_AUTH_COOKIE_PATH) {
|
|
||||||
store
|
|
||||||
.insert_raw(
|
|
||||||
&RawCookie::new("local", local),
|
|
||||||
&"http://localhost".parse()?,
|
|
||||||
)
|
|
||||||
.with_kind(crate::ErrorKind::Network)?;
|
|
||||||
}
|
|
||||||
store
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Ok(CliContext(Arc::new(CliContextSeed {
|
Ok(CliContext(Arc::new(CliContextSeed {
|
||||||
@@ -129,9 +125,17 @@ impl CliContext {
|
|||||||
Ok::<_, Error>(registry)
|
Ok::<_, Error>(registry)
|
||||||
})
|
})
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
|
registry_hostname: config.registry_hostname,
|
||||||
|
registry_listen: config.registry_listen,
|
||||||
|
tunnel_addr: config.tunnel,
|
||||||
|
tunnel_listen: config.tunnel_listen,
|
||||||
client: {
|
client: {
|
||||||
let mut builder = Client::builder().cookie_provider(cookie_store.clone());
|
let mut builder = Client::builder().cookie_provider(cookie_store.clone());
|
||||||
if let Some(proxy) = config.proxy {
|
if let Some(proxy) = config.proxy.or_else(|| {
|
||||||
|
config
|
||||||
|
.socks_listen
|
||||||
|
.and_then(|socks| format!("socks5h://{socks}").parse::<Url>().log_err())
|
||||||
|
}) {
|
||||||
builder =
|
builder =
|
||||||
builder.proxy(Proxy::all(proxy).with_kind(crate::ErrorKind::ParseUrl)?)
|
builder.proxy(Proxy::all(proxy).with_kind(crate::ErrorKind::ParseUrl)?)
|
||||||
}
|
}
|
||||||
@@ -139,14 +143,9 @@ impl CliContext {
|
|||||||
},
|
},
|
||||||
cookie_store,
|
cookie_store,
|
||||||
cookie_path,
|
cookie_path,
|
||||||
developer_key_path: config.developer_key_path.unwrap_or_else(|| {
|
developer_key_path: config
|
||||||
local_config_path()
|
.developer_key_path
|
||||||
.as_deref()
|
.unwrap_or_else(default_developer_key_path),
|
||||||
.unwrap_or_else(|| Path::new(super::config::CONFIG_PATH))
|
|
||||||
.parent()
|
|
||||||
.unwrap_or(Path::new("/"))
|
|
||||||
.join("developer.key.pem")
|
|
||||||
}),
|
|
||||||
developer_key: OnceCell::new(),
|
developer_key: OnceCell::new(),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
@@ -155,8 +154,9 @@ impl CliContext {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub fn developer_key(&self) -> Result<&ed25519_dalek::SigningKey, Error> {
|
pub fn developer_key(&self) -> Result<&ed25519_dalek::SigningKey, Error> {
|
||||||
self.developer_key.get_or_try_init(|| {
|
self.developer_key.get_or_try_init(|| {
|
||||||
if !self.developer_key_path.exists() {
|
for path in [Path::new(OS_DEVELOPER_KEY_PATH), &self.developer_key_path] {
|
||||||
return Err(Error::new(eyre!("Developer Key does not exist! Please run `start-cli init` before running this command."), crate::ErrorKind::Uninitialized));
|
if !path.exists() {
|
||||||
|
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(&self.developer_key_path)?,
|
||||||
@@ -168,7 +168,12 @@ impl CliContext {
|
|||||||
ErrorKind::OpenSsl,
|
ErrorKind::OpenSsl,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
Ok(secret.into())
|
return Ok(secret.into())
|
||||||
|
}
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("Developer Key does not exist! Please run `start-cli init` before running this command."),
|
||||||
|
crate::ErrorKind::Uninitialized
|
||||||
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +190,7 @@ impl CliContext {
|
|||||||
eyre!("Cannot parse scheme from base URL"),
|
eyre!("Cannot parse scheme from base URL"),
|
||||||
crate::ErrorKind::ParseUrl,
|
crate::ErrorKind::ParseUrl,
|
||||||
)
|
)
|
||||||
.into())
|
.into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
url.set_scheme(ws_scheme)
|
url.set_scheme(ws_scheme)
|
||||||
@@ -276,27 +281,90 @@ impl Context for CliContext {
|
|||||||
}
|
}
|
||||||
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> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
if let Ok(local) = std::fs::read_to_string(RpcContext::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(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
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(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<InitContext> for CliContext {
|
impl CallRemote<InitContext> 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> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
crate::middleware::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<SetupContext> for CliContext {
|
impl CallRemote<SetupContext> 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> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
crate::middleware::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CallRemote<InstallContext> for CliContext {
|
impl CallRemote<InstallContext> 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> {
|
||||||
call_remote_http(&self.client, self.rpc_url.clone(), method, params).await
|
crate::middleware::signature::call_remote(
|
||||||
|
self,
|
||||||
|
self.rpc_url.clone(),
|
||||||
|
self.rpc_url.host_str().or_not_found("rpc url hostname")?,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ use std::net::SocketAddr;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use imbl_value::InternedString;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::MAIN_DATA;
|
||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::IoFormat;
|
use crate::util::serde::IoFormat;
|
||||||
use crate::version::VersionT;
|
use crate::version::VersionT;
|
||||||
use crate::MAIN_DATA;
|
|
||||||
|
|
||||||
pub const DEVICE_CONFIG_PATH: &str = "/media/startos/config/config.yaml"; // "/media/startos/config/config.yaml";
|
pub const DEVICE_CONFIG_PATH: &str = "/media/startos/config/config.yaml"; // "/media/startos/config/config.yaml";
|
||||||
pub const CONFIG_PATH: &str = "/etc/startos/config.yaml";
|
pub const CONFIG_PATH: &str = "/etc/startos/config.yaml";
|
||||||
@@ -55,7 +56,6 @@ pub trait ContextConfig: DeserializeOwned + Default {
|
|||||||
#[derive(Debug, Default, Deserialize, Serialize, Parser)]
|
#[derive(Debug, Default, Deserialize, Serialize, Parser)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
#[command(rename_all = "kebab-case")]
|
#[command(rename_all = "kebab-case")]
|
||||||
#[command(name = "start-cli")]
|
|
||||||
#[command(version = crate::version::Current::default().semver().to_string())]
|
#[command(version = crate::version::Current::default().semver().to_string())]
|
||||||
pub struct ClientConfig {
|
pub struct ClientConfig {
|
||||||
#[arg(short = 'c', long)]
|
#[arg(short = 'c', long)]
|
||||||
@@ -64,8 +64,18 @@ pub struct ClientConfig {
|
|||||||
pub host: Option<Url>,
|
pub host: Option<Url>,
|
||||||
#[arg(short = 'r', long)]
|
#[arg(short = 'r', long)]
|
||||||
pub registry: Option<Url>,
|
pub registry: Option<Url>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub registry_hostname: Option<InternedString>,
|
||||||
|
#[arg(skip)]
|
||||||
|
pub registry_listen: Option<SocketAddr>,
|
||||||
|
#[arg(short = 't', long)]
|
||||||
|
pub tunnel: Option<SocketAddr>,
|
||||||
|
#[arg(skip)]
|
||||||
|
pub tunnel_listen: Option<SocketAddr>,
|
||||||
#[arg(short = 'p', long)]
|
#[arg(short = 'p', long)]
|
||||||
pub proxy: Option<Url>,
|
pub proxy: Option<Url>,
|
||||||
|
#[arg(skip)]
|
||||||
|
pub socks_listen: Option<SocketAddr>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub cookie_path: Option<PathBuf>,
|
pub cookie_path: Option<PathBuf>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
@@ -78,6 +88,8 @@ impl ContextConfig for ClientConfig {
|
|||||||
fn merge_with(&mut self, other: Self) {
|
fn merge_with(&mut self, other: Self) {
|
||||||
self.host = self.host.take().or(other.host);
|
self.host = self.host.take().or(other.host);
|
||||||
self.registry = self.registry.take().or(other.registry);
|
self.registry = self.registry.take().or(other.registry);
|
||||||
|
self.registry_hostname = self.registry_hostname.take().or(other.registry_hostname);
|
||||||
|
self.tunnel = self.tunnel.take().or(other.tunnel);
|
||||||
self.proxy = self.proxy.take().or(other.proxy);
|
self.proxy = self.proxy.take().or(other.proxy);
|
||||||
self.cookie_path = self.cookie_path.take().or(other.cookie_path);
|
self.cookie_path = self.cookie_path.take().or(other.cookie_path);
|
||||||
self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path);
|
self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path);
|
||||||
@@ -104,15 +116,15 @@ pub struct ServerConfig {
|
|||||||
#[arg(skip)]
|
#[arg(skip)]
|
||||||
pub os_partitions: Option<OsPartitionInfo>,
|
pub os_partitions: Option<OsPartitionInfo>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub tor_control: Option<SocketAddr>,
|
pub socks_listen: Option<SocketAddr>,
|
||||||
#[arg(long)]
|
|
||||||
pub tor_socks: Option<SocketAddr>,
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub revision_cache_size: Option<usize>,
|
pub revision_cache_size: Option<usize>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub disable_encryption: Option<bool>,
|
pub disable_encryption: Option<bool>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub multi_arch_s9pks: Option<bool>,
|
pub multi_arch_s9pks: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub developer_key_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
impl ContextConfig for ServerConfig {
|
impl ContextConfig for ServerConfig {
|
||||||
fn next(&mut self) -> Option<PathBuf> {
|
fn next(&mut self) -> Option<PathBuf> {
|
||||||
@@ -121,14 +133,14 @@ impl ContextConfig for ServerConfig {
|
|||||||
fn merge_with(&mut self, other: Self) {
|
fn merge_with(&mut self, other: Self) {
|
||||||
self.ethernet_interface = self.ethernet_interface.take().or(other.ethernet_interface);
|
self.ethernet_interface = self.ethernet_interface.take().or(other.ethernet_interface);
|
||||||
self.os_partitions = self.os_partitions.take().or(other.os_partitions);
|
self.os_partitions = self.os_partitions.take().or(other.os_partitions);
|
||||||
self.tor_control = self.tor_control.take().or(other.tor_control);
|
self.socks_listen = self.socks_listen.take().or(other.socks_listen);
|
||||||
self.tor_socks = self.tor_socks.take().or(other.tor_socks);
|
|
||||||
self.revision_cache_size = self
|
self.revision_cache_size = self
|
||||||
.revision_cache_size
|
.revision_cache_size
|
||||||
.take()
|
.take()
|
||||||
.or(other.revision_cache_size);
|
.or(other.revision_cache_size);
|
||||||
self.disable_encryption = self.disable_encryption.take().or(other.disable_encryption);
|
self.disable_encryption = self.disable_encryption.take().or(other.disable_encryption);
|
||||||
self.multi_arch_s9pks = self.multi_arch_s9pks.take().or(other.multi_arch_s9pks);
|
self.multi_arch_s9pks = self.multi_arch_s9pks.take().or(other.multi_arch_s9pks);
|
||||||
|
self.developer_key_path = self.developer_key_path.take().or(other.developer_key_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
|
||||||
use rpc_toolkit::Context;
|
use rpc_toolkit::Context;
|
||||||
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use tokio::sync::broadcast::Sender;
|
use tokio::sync::broadcast::Sender;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::rpc_continuations::RpcContinuations;
|
use crate::rpc_continuations::RpcContinuations;
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
pub struct DiagnosticContextSeed {
|
pub struct DiagnosticContextSeed {
|
||||||
pub shutdown: Sender<Shutdown>,
|
pub shutdown: Sender<Shutdown>,
|
||||||
|
|||||||
@@ -25,10 +25,12 @@ impl InitContext {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn init(cfg: &ServerConfig) -> Result<Self, Error> {
|
pub async fn init(cfg: &ServerConfig) -> Result<Self, Error> {
|
||||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||||
|
let mut progress = FullProgressTracker::new();
|
||||||
|
progress.enable_logging(true);
|
||||||
Ok(Self(Arc::new(InitContextSeed {
|
Ok(Self(Arc::new(InitContextSeed {
|
||||||
config: cfg.clone(),
|
config: cfg.clone(),
|
||||||
error: watch::channel(None).0,
|
error: watch::channel(None).0,
|
||||||
progress: FullProgressTracker::new(),
|
progress,
|
||||||
shutdown,
|
shutdown,
|
||||||
rpc_continuations: RpcContinuations::new(),
|
rpc_continuations: RpcContinuations::new(),
|
||||||
})))
|
})))
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ use rpc_toolkit::Context;
|
|||||||
use tokio::sync::broadcast::Sender;
|
use tokio::sync::broadcast::Sender;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
use crate::net::utils::find_eth_iface;
|
use crate::net::utils::find_eth_iface;
|
||||||
use crate::rpc_continuations::RpcContinuations;
|
use crate::rpc_continuations::RpcContinuations;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
pub struct InstallContextSeed {
|
pub struct InstallContextSeed {
|
||||||
pub ethernet_interface: String,
|
pub ethernet_interface: String,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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, Mutex, RwLock};
|
use tokio::sync::{broadcast, oneshot, watch, RwLock};
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
@@ -31,8 +31,9 @@ use crate::db::model::Database;
|
|||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
use crate::init::{check_time_is_synchronized, InitResult};
|
use crate::init::{check_time_is_synchronized, InitResult};
|
||||||
use crate::install::PKG_ARCHIVE_DIR;
|
use crate::install::PKG_ARCHIVE_DIR;
|
||||||
use crate::lxc::{ContainerId, LxcContainer, LxcManager};
|
use crate::lxc::LxcManager;
|
||||||
use crate::net::net_controller::{NetController, NetService};
|
use crate::net::net_controller::{NetController, NetService};
|
||||||
|
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::{UpgradableListener, WebServerAcceptorSetter};
|
||||||
use crate::net::wifi::WpaCli;
|
use crate::net::wifi::WpaCli;
|
||||||
@@ -46,7 +47,7 @@ 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, Watch};
|
||||||
use crate::DATA_DIR;
|
use crate::{DATA_DIR, HOST_IP};
|
||||||
|
|
||||||
pub struct RpcContextSeed {
|
pub struct RpcContextSeed {
|
||||||
is_closed: AtomicBool,
|
is_closed: AtomicBool,
|
||||||
@@ -65,7 +66,6 @@ pub struct RpcContextSeed {
|
|||||||
pub cancellable_installs: SyncMutex<BTreeMap<PackageId, oneshot::Sender<()>>>,
|
pub cancellable_installs: SyncMutex<BTreeMap<PackageId, oneshot::Sender<()>>>,
|
||||||
pub metrics_cache: Watch<Option<crate::system::Metrics>>,
|
pub metrics_cache: Watch<Option<crate::system::Metrics>>,
|
||||||
pub shutdown: broadcast::Sender<Option<Shutdown>>,
|
pub shutdown: broadcast::Sender<Option<Shutdown>>,
|
||||||
pub tor_socks: SocketAddr,
|
|
||||||
pub lxc_manager: Arc<LxcManager>,
|
pub lxc_manager: Arc<LxcManager>,
|
||||||
pub open_authed_continuations: OpenAuthedContinuations<Option<InternedString>>,
|
pub open_authed_continuations: OpenAuthedContinuations<Option<InternedString>>,
|
||||||
pub rpc_continuations: RpcContinuations,
|
pub rpc_continuations: RpcContinuations,
|
||||||
@@ -75,12 +75,6 @@ pub struct RpcContextSeed {
|
|||||||
pub client: Client,
|
pub client: Client,
|
||||||
pub start_time: Instant,
|
pub start_time: Instant,
|
||||||
pub crons: SyncMutex<BTreeMap<Guid, NonDetachingJoinHandle<()>>>,
|
pub crons: SyncMutex<BTreeMap<Guid, NonDetachingJoinHandle<()>>>,
|
||||||
// #[cfg(feature = "dev")]
|
|
||||||
pub dev: Dev,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Dev {
|
|
||||||
pub lxc: Mutex<BTreeMap<ContainerId, LxcContainer>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Hardware {
|
pub struct Hardware {
|
||||||
@@ -138,10 +132,7 @@ impl RpcContext {
|
|||||||
run_migrations,
|
run_migrations,
|
||||||
}: InitRpcContextPhases,
|
}: InitRpcContextPhases,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let tor_proxy = config.tor_socks.unwrap_or(SocketAddr::V4(SocketAddrV4::new(
|
let socks_proxy = config.socks_listen.unwrap_or(DEFAULT_SOCKS_LISTEN);
|
||||||
Ipv4Addr::new(127, 0, 0, 1),
|
|
||||||
9050,
|
|
||||||
)));
|
|
||||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||||
|
|
||||||
load_db.start();
|
load_db.start();
|
||||||
@@ -163,18 +154,9 @@ impl RpcContext {
|
|||||||
{
|
{
|
||||||
(net_ctrl, os_net_service)
|
(net_ctrl, os_net_service)
|
||||||
} else {
|
} else {
|
||||||
let net_ctrl = Arc::new(
|
let net_ctrl =
|
||||||
NetController::init(
|
Arc::new(NetController::init(db.clone(), &account.hostname, socks_proxy).await?);
|
||||||
db.clone(),
|
webserver.try_upgrade(|a| net_ctrl.net_iface.watcher.upgrade_listener(a))?;
|
||||||
config
|
|
||||||
.tor_control
|
|
||||||
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))),
|
|
||||||
tor_proxy,
|
|
||||||
&account.hostname,
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
webserver.try_upgrade(|a| net_ctrl.net_iface.upgrade_listener(a))?;
|
|
||||||
let os_net_service = net_ctrl.os_bindings().await?;
|
let os_net_service = net_ctrl.os_bindings().await?;
|
||||||
(net_ctrl, os_net_service)
|
(net_ctrl, os_net_service)
|
||||||
};
|
};
|
||||||
@@ -183,7 +165,7 @@ impl RpcContext {
|
|||||||
|
|
||||||
let services = ServiceMap::default();
|
let services = ServiceMap::default();
|
||||||
let metrics_cache = Watch::<Option<crate::system::Metrics>>::new(None);
|
let metrics_cache = Watch::<Option<crate::system::Metrics>>::new(None);
|
||||||
let tor_proxy_url = format!("socks5h://{tor_proxy}");
|
let socks_proxy_url = format!("socks5h://{socks_proxy}");
|
||||||
|
|
||||||
let crons = SyncMutex::new(BTreeMap::new());
|
let crons = SyncMutex::new(BTreeMap::new());
|
||||||
|
|
||||||
@@ -251,7 +233,6 @@ impl RpcContext {
|
|||||||
cancellable_installs: SyncMutex::new(BTreeMap::new()),
|
cancellable_installs: SyncMutex::new(BTreeMap::new()),
|
||||||
metrics_cache,
|
metrics_cache,
|
||||||
shutdown,
|
shutdown,
|
||||||
tor_socks: tor_proxy,
|
|
||||||
lxc_manager: Arc::new(LxcManager::new()),
|
lxc_manager: Arc::new(LxcManager::new()),
|
||||||
open_authed_continuations: OpenAuthedContinuations::new(),
|
open_authed_continuations: OpenAuthedContinuations::new(),
|
||||||
rpc_continuations: RpcContinuations::new(),
|
rpc_continuations: RpcContinuations::new(),
|
||||||
@@ -267,21 +248,11 @@ impl RpcContext {
|
|||||||
})?,
|
})?,
|
||||||
),
|
),
|
||||||
client: Client::builder()
|
client: Client::builder()
|
||||||
.proxy(Proxy::custom(move |url| {
|
.proxy(Proxy::all(socks_proxy_url)?)
|
||||||
if url.host_str().map_or(false, |h| h.ends_with(".onion")) {
|
|
||||||
Some(tor_proxy_url.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.build()
|
.build()
|
||||||
.with_kind(crate::ErrorKind::ParseUrl)?,
|
.with_kind(crate::ErrorKind::ParseUrl)?,
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
crons,
|
crons,
|
||||||
// #[cfg(feature = "dev")]
|
|
||||||
dev: Dev {
|
|
||||||
lxc: Mutex::new(BTreeMap::new()),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let res = Self(seed.clone());
|
let res = Self(seed.clone());
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ 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::util::net::WebSocketExt;
|
use crate::util::net::WebSocketExt;
|
||||||
use crate::MAIN_DATA;
|
use crate::MAIN_DATA;
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ impl TryFrom<&AccountInfo> for SetupResult {
|
|||||||
tor_addresses: value
|
tor_addresses: value
|
||||||
.tor_keys
|
.tor_keys
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tor_key| format!("https://{}", tor_key.public().get_onion_address()))
|
.map(|tor_key| format!("https://{}", tor_key.onion_address()))
|
||||||
.collect(),
|
.collect(),
|
||||||
hostname: value.hostname.clone(),
|
hostname: value.hostname.clone(),
|
||||||
lan_address: value.hostname.lan_address(),
|
lan_address: value.hostname.lan_address(),
|
||||||
@@ -71,7 +72,8 @@ pub struct SetupContextSeed {
|
|||||||
pub progress: FullProgressTracker,
|
pub progress: FullProgressTracker,
|
||||||
pub task: OnceCell<NonDetachingJoinHandle<()>>,
|
pub task: OnceCell<NonDetachingJoinHandle<()>>,
|
||||||
pub result: OnceCell<Result<(SetupResult, RpcContext), Error>>,
|
pub result: OnceCell<Result<(SetupResult, RpcContext), Error>>,
|
||||||
pub shutdown: Sender<()>,
|
pub disk_guid: OnceCell<Arc<String>>,
|
||||||
|
pub shutdown: Sender<Option<Shutdown>>,
|
||||||
pub rpc_continuations: RpcContinuations,
|
pub rpc_continuations: RpcContinuations,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +86,8 @@ impl SetupContext {
|
|||||||
config: &ServerConfig,
|
config: &ServerConfig,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
let (shutdown, _) = tokio::sync::broadcast::channel(1);
|
||||||
|
let mut progress = FullProgressTracker::new();
|
||||||
|
progress.enable_logging(true);
|
||||||
Ok(Self(Arc::new(SetupContextSeed {
|
Ok(Self(Arc::new(SetupContextSeed {
|
||||||
webserver: webserver.acceptor_setter(),
|
webserver: webserver.acceptor_setter(),
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
@@ -94,9 +98,10 @@ impl SetupContext {
|
|||||||
)
|
)
|
||||||
})?,
|
})?,
|
||||||
disable_encryption: config.disable_encryption.unwrap_or(false),
|
disable_encryption: config.disable_encryption.unwrap_or(false),
|
||||||
progress: FullProgressTracker::new(),
|
progress,
|
||||||
task: OnceCell::new(),
|
task: OnceCell::new(),
|
||||||
result: OnceCell::new(),
|
result: OnceCell::new(),
|
||||||
|
disk_guid: OnceCell::new(),
|
||||||
shutdown,
|
shutdown,
|
||||||
rpc_continuations: RpcContinuations::new(),
|
rpc_continuations: RpcContinuations::new(),
|
||||||
})))
|
})))
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use itertools::Itertools;
|
|||||||
use patch_db::json_ptr::{JsonPointer, ROOT};
|
use patch_db::json_ptr::{JsonPointer, ROOT};
|
||||||
use patch_db::{DiffPatch, Dump, Revision};
|
use patch_db::{DiffPatch, Dump, Revision};
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
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::sync::mpsc::{self, UnboundedReceiver};
|
use tokio::sync::mpsc::{self, UnboundedReceiver};
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
@@ -23,7 +23,7 @@ use crate::context::{CliContext, RpcContext};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||||
use crate::util::net::WebSocketExt;
|
use crate::util::net::WebSocketExt;
|
||||||
use crate::util::serde::{apply_expr, HandlerExtSerde};
|
use crate::util::serde::{HandlerExtSerde, apply_expr};
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref PUBLIC: JsonPointer = "/public".parse().unwrap();
|
static ref PUBLIC: JsonPointer = "/public".parse().unwrap();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::net::forward::AvailablePorts;
|
|||||||
use crate::net::keys::KeyStore;
|
use crate::net::keys::KeyStore;
|
||||||
use crate::notifications::Notifications;
|
use crate::notifications::Notifications;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::sign::AnyVerifyingKey;
|
||||||
use crate::ssh::SshKeys;
|
use crate::ssh::SshKeys;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
@@ -33,6 +34,9 @@ impl Database {
|
|||||||
private: Private {
|
private: Private {
|
||||||
key_store: KeyStore::new(account)?,
|
key_store: KeyStore::new(account)?,
|
||||||
password: account.password.clone(),
|
password: account.password.clone(),
|
||||||
|
auth_pubkeys: [AnyVerifyingKey::Ed25519((&account.developer_key).into())]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
ssh_privkey: Pem(account.ssh_key.clone()),
|
ssh_privkey: Pem(account.ssh_key.clone()),
|
||||||
ssh_pubkeys: SshKeys::new(),
|
ssh_pubkeys: SshKeys::new(),
|
||||||
available_ports: AvailablePorts::new(),
|
available_ports: AvailablePorts::new(),
|
||||||
@@ -40,7 +44,7 @@ impl Database {
|
|||||||
notifications: Notifications::new(),
|
notifications: Notifications::new(),
|
||||||
cifs: CifsTargets::new(),
|
cifs: CifsTargets::new(),
|
||||||
package_stores: BTreeMap::new(),
|
package_stores: BTreeMap::new(),
|
||||||
compat_s9pk_key: Pem(account.compat_s9pk_key.clone()),
|
developer_key: Pem(account.developer_key.clone()),
|
||||||
}, // TODO
|
}, // TODO
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use chrono::{DateTime, Utc};
|
|||||||
use exver::VersionRange;
|
use exver::VersionRange;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use models::{ActionId, DataUrl, HealthCheckId, HostId, PackageId, ReplayId, ServiceInterfaceId};
|
use models::{ActionId, DataUrl, HealthCheckId, HostId, PackageId, ReplayId, ServiceInterfaceId};
|
||||||
use patch_db::json_ptr::JsonPointer;
|
|
||||||
use patch_db::HasModel;
|
use patch_db::HasModel;
|
||||||
|
use patch_db::json_ptr::JsonPointer;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -17,7 +17,7 @@ use crate::prelude::*;
|
|||||||
use crate::progress::FullProgress;
|
use crate::progress::FullProgress;
|
||||||
use crate::s9pk::manifest::Manifest;
|
use crate::s9pk::manifest::Manifest;
|
||||||
use crate::status::MainStatus;
|
use crate::status::MainStatus;
|
||||||
use crate::util::serde::{is_partial_of, Pem};
|
use crate::util::serde::{Pem, is_partial_of};
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize, TS)]
|
#[derive(Debug, Default, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@@ -268,7 +268,7 @@ impl Model<PackageState> {
|
|||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("could not determine package state to get manifest"),
|
eyre!("could not determine package state to get manifest"),
|
||||||
ErrorKind::Database,
|
ErrorKind::Database,
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -375,7 +375,6 @@ pub struct PackageDataEntry {
|
|||||||
pub last_backup: Option<DateTime<Utc>>,
|
pub last_backup: Option<DateTime<Utc>>,
|
||||||
pub current_dependencies: CurrentDependencies,
|
pub current_dependencies: CurrentDependencies,
|
||||||
pub actions: BTreeMap<ActionId, ActionMetadata>,
|
pub actions: BTreeMap<ActionId, ActionMetadata>,
|
||||||
#[ts(as = "BTreeMap::<String, TaskEntry>")]
|
|
||||||
pub tasks: BTreeMap<ReplayId, TaskEntry>,
|
pub tasks: BTreeMap<ReplayId, TaskEntry>,
|
||||||
pub service_interfaces: BTreeMap<ServiceInterfaceId, ServiceInterface>,
|
pub service_interfaces: BTreeMap<ServiceInterfaceId, ServiceInterface>,
|
||||||
pub hosts: Hosts,
|
pub hosts: Hosts,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, HashSet};
|
||||||
|
|
||||||
use models::PackageId;
|
use models::PackageId;
|
||||||
use patch_db::{HasModel, Value};
|
use patch_db::{HasModel, Value};
|
||||||
@@ -10,6 +10,7 @@ use crate::net::forward::AvailablePorts;
|
|||||||
use crate::net::keys::KeyStore;
|
use crate::net::keys::KeyStore;
|
||||||
use crate::notifications::Notifications;
|
use crate::notifications::Notifications;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::sign::AnyVerifyingKey;
|
||||||
use crate::ssh::SshKeys;
|
use crate::ssh::SshKeys;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
@@ -19,8 +20,9 @@ use crate::util::serde::Pem;
|
|||||||
pub struct Private {
|
pub struct Private {
|
||||||
pub key_store: KeyStore,
|
pub key_store: KeyStore,
|
||||||
pub password: String, // argon2 hash
|
pub password: String, // argon2 hash
|
||||||
#[serde(default = "generate_compat_key")]
|
pub auth_pubkeys: HashSet<AnyVerifyingKey>,
|
||||||
pub compat_s9pk_key: Pem<ed25519_dalek::SigningKey>,
|
#[serde(default = "generate_developer_key")]
|
||||||
|
pub developer_key: Pem<ed25519_dalek::SigningKey>,
|
||||||
pub ssh_privkey: Pem<ssh_key::PrivateKey>,
|
pub ssh_privkey: Pem<ssh_key::PrivateKey>,
|
||||||
pub ssh_pubkeys: SshKeys,
|
pub ssh_pubkeys: SshKeys,
|
||||||
pub available_ports: AvailablePorts,
|
pub available_ports: AvailablePorts,
|
||||||
@@ -31,7 +33,7 @@ pub struct Private {
|
|||||||
pub package_stores: BTreeMap<PackageId, Value>,
|
pub package_stores: BTreeMap<PackageId, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_compat_key() -> Pem<ed25519_dalek::SigningKey> {
|
pub fn generate_developer_key() -> Pem<ed25519_dalek::SigningKey> {
|
||||||
Pem(ed25519_dalek::SigningKey::generate(
|
Pem(ed25519_dalek::SigningKey::generate(
|
||||||
&mut ssh_key::rand_core::OsRng::default(),
|
&mut ssh_key::rand_core::OsRng::default(),
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use exver::{Version, VersionRange};
|
use exver::{Version, VersionRange};
|
||||||
|
use imbl::{OrdMap, OrdSet};
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use ipnet::IpNet;
|
use ipnet::IpNet;
|
||||||
use isocountry::CountryCode;
|
use isocountry::CountryCode;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::PackageId;
|
use lazy_static::lazy_static;
|
||||||
|
use models::{GatewayId, PackageId};
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::hash::MessageDigest;
|
||||||
use patch_db::{HasModel, Value};
|
use patch_db::{HasModel, Value};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -16,6 +18,7 @@ use ts_rs::TS;
|
|||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
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::binding::{AddSslOptions, BindInfo, BindOptions, NetInfo};
|
||||||
use crate::net::host::Host;
|
use crate::net::host::Host;
|
||||||
use crate::net::utils::ipv6_is_local;
|
use crate::net::utils::ipv6_is_local;
|
||||||
@@ -27,7 +30,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, PLATFORM};
|
use crate::{ARCH, HOST_IP, PLATFORM};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -71,26 +74,25 @@ impl Public {
|
|||||||
net: NetInfo {
|
net: NetInfo {
|
||||||
assigned_port: None,
|
assigned_port: None,
|
||||||
assigned_ssl_port: Some(443),
|
assigned_ssl_port: Some(443),
|
||||||
public: false,
|
private_disabled: OrdSet::new(),
|
||||||
|
public_enabled: OrdSet::new(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
onions: account
|
onions: account.tor_keys.iter().map(|k| k.onion_address()).collect(),
|
||||||
.tor_keys
|
public_domains: BTreeMap::new(),
|
||||||
.iter()
|
private_domains: BTreeSet::new(),
|
||||||
.map(|k| k.public().get_onion_address())
|
|
||||||
.collect(),
|
|
||||||
domains: BTreeMap::new(),
|
|
||||||
hostname_info: BTreeMap::new(),
|
hostname_info: BTreeMap::new(),
|
||||||
},
|
},
|
||||||
wifi: WifiInfo {
|
wifi: WifiInfo {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
network_interfaces: BTreeMap::new(),
|
gateways: OrdMap::new(),
|
||||||
acme: BTreeMap::new(),
|
acme: BTreeMap::new(),
|
||||||
|
dns: Default::default(),
|
||||||
},
|
},
|
||||||
status_info: ServerStatus {
|
status_info: ServerStatus {
|
||||||
backup_progress: None,
|
backup_progress: None,
|
||||||
@@ -186,11 +188,22 @@ pub struct ServerInfo {
|
|||||||
pub struct NetworkInfo {
|
pub struct NetworkInfo {
|
||||||
pub wifi: WifiInfo,
|
pub wifi: WifiInfo,
|
||||||
pub host: Host,
|
pub host: Host,
|
||||||
#[ts(as = "BTreeMap::<String, NetworkInterfaceInfo>")]
|
#[ts(as = "BTreeMap::<GatewayId, NetworkInterfaceInfo>")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub network_interfaces: BTreeMap<InternedString, NetworkInterfaceInfo>,
|
pub gateways: OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub acme: BTreeMap<AcmeProvider, AcmeSettings>,
|
pub acme: BTreeMap<AcmeProvider, AcmeSettings>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub dns: DnsSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct DnsSettings {
|
||||||
|
pub dhcp_servers: Vec<SocketAddr>,
|
||||||
|
pub static_servers: Option<Vec<SocketAddr>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
@@ -198,13 +211,69 @@ pub struct NetworkInfo {
|
|||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct NetworkInterfaceInfo {
|
pub struct NetworkInterfaceInfo {
|
||||||
pub inbound: Option<bool>,
|
pub name: Option<InternedString>,
|
||||||
pub outbound: Option<bool>,
|
pub public: Option<bool>,
|
||||||
|
pub secure: Option<bool>,
|
||||||
pub ip_info: Option<IpInfo>,
|
pub ip_info: Option<IpInfo>,
|
||||||
}
|
}
|
||||||
impl NetworkInterfaceInfo {
|
impl NetworkInterfaceInfo {
|
||||||
pub fn inbound(&self) -> bool {
|
pub fn loopback() -> (&'static GatewayId, &'static Self) {
|
||||||
self.inbound.unwrap_or_else(|| {
|
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 {
|
||||||
|
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| {
|
||||||
let ip4s = ip_info
|
let ip4s = ip_info
|
||||||
.subnets
|
.subnets
|
||||||
@@ -218,11 +287,9 @@ impl NetworkInterfaceInfo {
|
|||||||
})
|
})
|
||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeSet<_>>();
|
||||||
if !ip4s.is_empty() {
|
if !ip4s.is_empty() {
|
||||||
return ip4s.iter().all(|ip4| {
|
return ip4s
|
||||||
ip4.is_loopback()
|
.iter()
|
||||||
|| (ip4.is_private() && !ip4.octets().starts_with(&[10, 59])) // reserving 10.59 for public wireguard configurations
|
.all(|ip4| ip4.is_loopback() || ip4.is_private() || ip4.is_link_local());
|
||||||
|| ip4.is_link_local()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
ip_info.subnets.iter().all(|ipnet| {
|
ip_info.subnets.iter().all(|ipnet| {
|
||||||
if let IpAddr::V6(ip6) = ipnet.addr() {
|
if let IpAddr::V6(ip6) = ipnet.addr() {
|
||||||
@@ -234,6 +301,14 @@ impl NetworkInterfaceInfo {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn secure(&self) -> bool {
|
||||||
|
self.secure.unwrap_or_else(|| {
|
||||||
|
self.ip_info.as_ref().map_or(false, |ip_info| {
|
||||||
|
ip_info.device_type == Some(NetworkInterfaceType::Wireguard)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize, TS, HasModel)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize, TS, HasModel)]
|
||||||
@@ -246,10 +321,14 @@ pub struct IpInfo {
|
|||||||
pub scope_id: u32,
|
pub scope_id: u32,
|
||||||
pub device_type: Option<NetworkInterfaceType>,
|
pub device_type: Option<NetworkInterfaceType>,
|
||||||
#[ts(type = "string[]")]
|
#[ts(type = "string[]")]
|
||||||
pub subnets: BTreeSet<IpNet>,
|
pub subnets: OrdSet<IpNet>,
|
||||||
|
#[ts(type = "string[]")]
|
||||||
|
pub lan_ip: OrdSet<IpAddr>,
|
||||||
pub wan_ip: Option<Ipv4Addr>,
|
pub wan_ip: Option<Ipv4Addr>,
|
||||||
#[ts(type = "string[]")]
|
#[ts(type = "string[]")]
|
||||||
pub ntp_servers: BTreeSet<InternedString>,
|
pub ntp_servers: OrdSet<InternedString>,
|
||||||
|
#[ts(type = "string[]")]
|
||||||
|
pub dns_servers: OrdSet<IpAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, TS)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize, TS)]
|
||||||
@@ -269,6 +348,14 @@ pub struct AcmeSettings {
|
|||||||
pub contact: Vec<String>,
|
pub contact: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct DomainSettings {
|
||||||
|
pub gateway: GatewayId,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::marker::PhantomData;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use imbl::OrdMap;
|
||||||
pub use imbl_value::Value;
|
pub use imbl_value::Value;
|
||||||
use patch_db::value::InternedString;
|
use patch_db::value::InternedString;
|
||||||
pub use patch_db::{HasModel, MutateResult, PatchDb};
|
pub use patch_db::{HasModel, MutateResult, PatchDb};
|
||||||
@@ -199,6 +200,18 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<A, B> Map for OrdMap<A, B>
|
||||||
|
where
|
||||||
|
A: serde::Serialize + serde::de::DeserializeOwned + Clone + Ord + AsRef<str>,
|
||||||
|
B: serde::Serialize + serde::de::DeserializeOwned + Clone,
|
||||||
|
{
|
||||||
|
type Key = A;
|
||||||
|
type Value = B;
|
||||||
|
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||||
|
Ok(key.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Map> Model<T>
|
impl<T: Map> Model<T>
|
||||||
where
|
where
|
||||||
T::Value: Serialize,
|
T::Value: Serialize,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use models::PackageId;
|
use models::PackageId;
|
||||||
@@ -24,20 +25,62 @@ impl Map for Dependencies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
#[ts(export)]
|
|
||||||
pub struct DepInfo {
|
pub struct DepInfo {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub optional: bool,
|
pub optional: bool,
|
||||||
pub s9pk: Option<PathOrUrl>,
|
#[serde(flatten)]
|
||||||
|
pub metadata: Option<MetadataSrc>,
|
||||||
|
}
|
||||||
|
impl TS for DepInfo {
|
||||||
|
type WithoutGenerics = Self;
|
||||||
|
fn decl() -> String {
|
||||||
|
format!("type {} = {}", Self::name(), Self::inline())
|
||||||
|
}
|
||||||
|
fn decl_concrete() -> String {
|
||||||
|
Self::decl()
|
||||||
|
}
|
||||||
|
fn name() -> String {
|
||||||
|
"DepInfo".into()
|
||||||
|
}
|
||||||
|
fn inline() -> String {
|
||||||
|
"{ description: string | null, optional: boolean } & MetadataSrc".into()
|
||||||
|
}
|
||||||
|
fn inline_flattened() -> String {
|
||||||
|
Self::inline()
|
||||||
|
}
|
||||||
|
fn visit_dependencies(v: &mut impl ts_rs::TypeVisitor)
|
||||||
|
where
|
||||||
|
Self: 'static,
|
||||||
|
{
|
||||||
|
v.visit::<MetadataSrc>()
|
||||||
|
}
|
||||||
|
fn output_path() -> Option<&'static std::path::Path> {
|
||||||
|
Some(Path::new("DepInfo.ts"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export)]
|
||||||
|
pub enum MetadataSrc {
|
||||||
|
Metadata(Metadata),
|
||||||
|
S9pk(Option<PathOrUrl>), // backwards compatibility
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct Metadata {
|
||||||
|
pub title: InternedString,
|
||||||
|
pub icon: PathOrUrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
#[ts(export)]
|
|
||||||
pub struct DependencyMetadata {
|
pub struct DependencyMetadata {
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub title: InternedString,
|
pub title: InternedString,
|
||||||
|
|||||||
@@ -1,40 +1,57 @@
|
|||||||
use std::fs::File;
|
use std::path::{Path, PathBuf};
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use ed25519::pkcs8::EncodePrivateKey;
|
|
||||||
use ed25519::PublicKeyBytes;
|
use ed25519::PublicKeyBytes;
|
||||||
|
use ed25519::pkcs8::EncodePrivateKey;
|
||||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
|
use crate::context::config::local_config_path;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::util::io::create_file_mod;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
pub const OS_DEVELOPER_KEY_PATH: &str = "/run/startos/developer.key.pem";
|
||||||
pub fn init(ctx: CliContext) -> Result<(), Error> {
|
|
||||||
if !ctx.developer_key_path.exists() {
|
pub fn default_developer_key_path() -> PathBuf {
|
||||||
let parent = ctx.developer_key_path.parent().unwrap_or(Path::new("/"));
|
local_config_path()
|
||||||
if !parent.exists() {
|
.as_deref()
|
||||||
std::fs::create_dir_all(parent)
|
.unwrap_or_else(|| Path::new(crate::context::config::CONFIG_PATH))
|
||||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, parent.display().to_string()))?;
|
.parent()
|
||||||
|
.unwrap_or(Path::new("/"))
|
||||||
|
.join("developer.key.pem")
|
||||||
}
|
}
|
||||||
tracing::info!("Generating new developer key...");
|
|
||||||
let secret = SigningKey::generate(&mut ssh_key::rand_core::OsRng::default());
|
pub async fn write_developer_key(
|
||||||
tracing::info!("Writing key to {}", ctx.developer_key_path.display());
|
secret: &ed25519_dalek::SigningKey,
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let keypair_bytes = ed25519::KeypairBytes {
|
let keypair_bytes = ed25519::KeypairBytes {
|
||||||
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 dev_key_file = File::create(&ctx.developer_key_path)
|
let mut file = create_file_mod(path, 0o046).await?;
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, ctx.developer_key_path.display()))?;
|
file.write_all(
|
||||||
dev_key_file.write_all(
|
|
||||||
keypair_bytes
|
keypair_bytes
|
||||||
.to_pkcs8_pem(base64ct::LineEnding::default())
|
.to_pkcs8_pem(base64ct::LineEnding::default())
|
||||||
.with_kind(crate::ErrorKind::Pem)?
|
.with_kind(crate::ErrorKind::Pem)?
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
)?;
|
)
|
||||||
dev_key_file.sync_all()?;
|
.await?;
|
||||||
|
file.sync_all().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn init(ctx: CliContext) -> Result<(), Error> {
|
||||||
|
if tokio::fs::metadata(OS_DEVELOPER_KEY_PATH).await.is_ok() {
|
||||||
|
println!("Developer key already exists at {}", OS_DEVELOPER_KEY_PATH);
|
||||||
|
} else if tokio::fs::metadata(&ctx.developer_key_path).await.is_err() {
|
||||||
|
tracing::info!("Generating new developer key...");
|
||||||
|
let secret = SigningKey::generate(&mut ssh_key::rand_core::OsRng::default());
|
||||||
|
tracing::info!("Writing key to {}", ctx.developer_key_path.display());
|
||||||
|
write_developer_key(&secret, &ctx.developer_key_path).await?;
|
||||||
println!(
|
println!(
|
||||||
"New developer key generated at {}",
|
"New developer key generated at {}",
|
||||||
ctx.developer_key_path.display()
|
ctx.developer_key_path.display()
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{
|
use rpc_toolkit::{
|
||||||
from_fn, from_fn_async, CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler,
|
CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, from_fn, from_fn_async,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::context::{CliContext, DiagnosticContext, RpcContext};
|
use crate::context::{CliContext, DiagnosticContext, RpcContext};
|
||||||
@@ -12,7 +11,6 @@ use crate::init::SYSTEM_REBUILD_PATH;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
use crate::util::io::delete_file;
|
use crate::util::io::delete_file;
|
||||||
use crate::DATA_DIR;
|
|
||||||
|
|
||||||
pub fn diagnostic<C: Context>() -> ParentHandler<C> {
|
pub fn diagnostic<C: Context>() -> ParentHandler<C> {
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
@@ -70,10 +68,7 @@ pub fn error(ctx: DiagnosticContext) -> Result<Arc<RpcError>, Error> {
|
|||||||
pub fn restart(ctx: DiagnosticContext) -> Result<(), Error> {
|
pub fn restart(ctx: DiagnosticContext) -> Result<(), Error> {
|
||||||
ctx.shutdown
|
ctx.shutdown
|
||||||
.send(Shutdown {
|
.send(Shutdown {
|
||||||
export_args: ctx
|
disk_guid: ctx.disk_guid.clone(),
|
||||||
.disk_guid
|
|
||||||
.clone()
|
|
||||||
.map(|guid| (guid, Path::new(DATA_DIR).to_owned())),
|
|
||||||
restart: true,
|
restart: true,
|
||||||
})
|
})
|
||||||
.map_err(|_| eyre!("receiver dropped"))
|
.map_err(|_| eyre!("receiver dropped"))
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ use std::path::Path;
|
|||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
use crate::disk::fsck::RequiresReboot;
|
use crate::disk::fsck::RequiresReboot;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn btrfs_check_readonly(logicalname: impl AsRef<Path>) -> Result<RequiresReboot, Error> {
|
pub async fn btrfs_check_readonly(logicalname: impl AsRef<Path>) -> Result<RequiresReboot, Error> {
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ use std::ffi::OsStr;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use futures::future::BoxFuture;
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
use futures::future::BoxFuture;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::disk::fsck::RequiresReboot;
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
use crate::disk::fsck::RequiresReboot;
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn e2fsck_preen(
|
pub async fn e2fsck_preen(
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ use std::path::Path;
|
|||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
use crate::disk::fsck::btrfs::{btrfs_check_readonly, btrfs_check_repair};
|
use crate::disk::fsck::btrfs::{btrfs_check_readonly, btrfs_check_repair};
|
||||||
use crate::disk::fsck::ext4::{e2fsck_aggressive, e2fsck_preen};
|
use crate::disk::fsck::ext4::{e2fsck_aggressive, e2fsck_preen};
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
pub mod btrfs;
|
pub mod btrfs;
|
||||||
pub mod ext4;
|
pub mod ext4;
|
||||||
@@ -45,7 +45,7 @@ impl RepairStrategy {
|
|||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("Unknown filesystem {fs}"),
|
eyre!("Unknown filesystem {fs}"),
|
||||||
crate::ErrorKind::DiskManagement,
|
crate::ErrorKind::DiskManagement,
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lazy_format::lazy_format;
|
use lazy_format::lazy_format;
|
||||||
use rpc_toolkit::{from_fn_async, CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler};
|
use rpc_toolkit::{CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, from_fn_async};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::Error;
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::disk::util::DiskInfo;
|
use crate::disk::util::DiskInfo;
|
||||||
use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
|
use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable};
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
pub mod fsck;
|
pub mod fsck;
|
||||||
pub mod main;
|
pub mod main;
|
||||||
@@ -96,14 +96,13 @@ fn display_disk_info(params: WithIoFormat<Empty>, args: Vec<DiskInfo>) -> Result
|
|||||||
"N/A"
|
"N/A"
|
||||||
},
|
},
|
||||||
part.capacity,
|
part.capacity,
|
||||||
if let Some(used) = part
|
&if let Some(used) = part
|
||||||
.used
|
.used
|
||||||
.map(|u| format!("{:.2} GiB", u as f64 / 1024.0 / 1024.0 / 1024.0))
|
.map(|u| format!("{:.2} GiB", u as f64 / 1024.0 / 1024.0 / 1024.0))
|
||||||
.as_ref()
|
|
||||||
{
|
{
|
||||||
used
|
used
|
||||||
} else {
|
} else {
|
||||||
"N/A"
|
"N/A".to_owned()
|
||||||
},
|
},
|
||||||
&if part.start_os.is_empty() {
|
&if part.start_os.is_empty() {
|
||||||
"N/A".to_owned()
|
"N/A".to_owned()
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use tracing::instrument;
|
|||||||
use super::guard::{GenericMountGuard, TmpMountGuard};
|
use super::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
use crate::auth::check_password;
|
use crate::auth::check_password;
|
||||||
use crate::backup::target::BackupInfo;
|
use crate::backup::target::BackupInfo;
|
||||||
use crate::disk::mount::filesystem::backupfs::BackupFS;
|
|
||||||
use crate::disk::mount::filesystem::ReadWrite;
|
use crate::disk::mount::filesystem::ReadWrite;
|
||||||
|
use crate::disk::mount::filesystem::backupfs::BackupFS;
|
||||||
use crate::disk::mount::guard::SubPath;
|
use crate::disk::mount::guard::SubPath;
|
||||||
use crate::disk::util::StartOsRecoveryInfo;
|
use crate::disk::util::StartOsRecoveryInfo;
|
||||||
use crate::util::crypto::{decrypt_slice, encrypt_slice};
|
use crate::util::crypto::{decrypt_slice, encrypt_slice};
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ use tracing::instrument;
|
|||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use super::{FileSystem, MountType, ReadOnly};
|
use super::{FileSystem, MountType, ReadOnly};
|
||||||
|
use crate::Error;
|
||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
async fn resolve_hostname(hostname: &str) -> Result<IpAddr, Error> {
|
async fn resolve_hostname(hostname: &str) -> Result<IpAddr, Error> {
|
||||||
if let Ok(addr) = hostname.parse() {
|
if let Ok(addr) = hostname.parse() {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
|
||||||
use super::{FileSystem, MountType};
|
use super::{FileSystem, MountType};
|
||||||
use crate::util::Invoke;
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
use crate::util::Invoke;
|
||||||
|
|
||||||
pub async fn mount_httpdirfs(url: &Url, mountpoint: impl AsRef<Path>) -> Result<(), Error> {
|
pub async fn mount_httpdirfs(url: &Url, mountpoint: impl AsRef<Path>) -> Result<(), Error> {
|
||||||
tokio::fs::create_dir_all(mountpoint.as_ref()).await?;
|
tokio::fs::create_dir_all(mountpoint.as_ref()).await?;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::ffi::OsStr;
|
|||||||
use std::fmt::{Display, Write};
|
use std::fmt::{Display, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use digest::generic_array::GenericArray;
|
|
||||||
use digest::OutputSizeUser;
|
use digest::OutputSizeUser;
|
||||||
|
use digest::generic_array::GenericArray;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -106,6 +106,7 @@ pub trait FileSystem: Send + Sync {
|
|||||||
}
|
}
|
||||||
fn source_hash(
|
fn source_hash(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Future<Output = Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error>>
|
) -> impl Future<
|
||||||
+ Send;
|
Output = Result<GenericArray<u8, <Sha256 as OutputSizeUser>::OutputSize>, Error>,
|
||||||
|
> + Send;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,8 @@ impl<P0: AsRef<Path>, P1: AsRef<Path>, P2: AsRef<Path>> OverlayFs<P0, P1, P2> {
|
|||||||
Self { lower, upper, work }
|
Self { lower, upper, work }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<
|
impl<P0: AsRef<Path> + Send + Sync, P1: AsRef<Path> + Send + Sync, P2: AsRef<Path> + Send + Sync>
|
||||||
P0: AsRef<Path> + Send + Sync,
|
FileSystem for OverlayFs<P0, P1, P2>
|
||||||
P1: AsRef<Path> + Send + Sync,
|
|
||||||
P2: AsRef<Path> + Send + Sync,
|
|
||||||
> FileSystem for OverlayFs<P0, P1, P2>
|
|
||||||
{
|
{
|
||||||
fn mount_type(&self) -> Option<impl AsRef<str>> {
|
fn mount_type(&self) -> Option<impl AsRef<str>> {
|
||||||
Some("overlay")
|
Some("overlay")
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use tracing::instrument;
|
|||||||
|
|
||||||
use super::filesystem::{FileSystem, MountType, ReadOnly, ReadWrite};
|
use super::filesystem::{FileSystem, MountType, ReadOnly, ReadWrite};
|
||||||
use super::util::unmount;
|
use super::util::unmount;
|
||||||
use crate::util::{Invoke, Never};
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
use crate::util::{Invoke, Never};
|
||||||
|
|
||||||
pub const TMP_MOUNTPOINT: &'static str = "/media/startos/tmp";
|
pub const TMP_MOUNTPOINT: &'static str = "/media/startos/tmp";
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::path::Path;
|
|||||||
|
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::util::Invoke;
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
use crate::util::Invoke;
|
||||||
|
|
||||||
pub async fn is_mountpoint(path: impl AsRef<Path>) -> Result<bool, Error> {
|
pub async fn is_mountpoint(path: impl AsRef<Path>) -> Result<bool, Error> {
|
||||||
let is_mountpoint = tokio::process::Command::new("mountpoint")
|
let is_mountpoint = tokio::process::Command::new("mountpoint")
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::mount::filesystem::block_dev::BlockDev;
|
|
||||||
use super::mount::filesystem::ReadOnly;
|
use super::mount::filesystem::ReadOnly;
|
||||||
|
use super::mount::filesystem::block_dev::BlockDev;
|
||||||
use super::mount::guard::TmpMountGuard;
|
use super::mount::guard::TmpMountGuard;
|
||||||
use crate::disk::mount::guard::GenericMountGuard;
|
|
||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
|
use crate::disk::mount::guard::GenericMountGuard;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::Hostname;
|
||||||
use crate::util::serde::IoFormat;
|
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
|
use crate::util::serde::IoFormat;
|
||||||
use crate::{Error, ResultExt as _};
|
use crate::{Error, ResultExt as _};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tokio::io::BufReader;
|
use tokio::io::BufReader;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::PLATFORM;
|
||||||
use crate::disk::fsck::RequiresReboot;
|
use crate::disk::fsck::RequiresReboot;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::io::open_file;
|
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::PLATFORM;
|
use crate::util::io::open_file;
|
||||||
|
|
||||||
/// Part of the Firmware, look there for more about
|
/// Part of the Firmware, look there for more about
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use lazy_format::lazy_format;
|
use lazy_format::lazy_format;
|
||||||
use rand::{rng, Rng};
|
use rand::{Rng, rng};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
@@ -35,8 +35,8 @@ impl Hostname {
|
|||||||
|
|
||||||
pub fn generate_hostname() -> Hostname {
|
pub fn generate_hostname() -> Hostname {
|
||||||
let mut rng = rng();
|
let mut rng = rng();
|
||||||
let adjective = &ADJECTIVES[rng.gen_range(0..ADJECTIVES.len())];
|
let adjective = &ADJECTIVES[rng.random_range(0..ADJECTIVES.len())];
|
||||||
let noun = &NOUNS[rng.gen_range(0..NOUNS.len())];
|
let noun = &NOUNS[rng.random_range(0..NOUNS.len())];
|
||||||
Hostname(InternedString::from_display(&lazy_format!(
|
Hostname(InternedString::from_display(&lazy_format!(
|
||||||
"{adjective}-{noun}"
|
"{adjective}-{noun}"
|
||||||
)))
|
)))
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
use std::fs::Permissions;
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use axum::extract::ws::{self};
|
use axum::extract::ws;
|
||||||
use color_eyre::eyre::eyre;
|
|
||||||
use const_format::formatcp;
|
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 rand::random;
|
|
||||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -21,13 +16,14 @@ 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};
|
use crate::context::{CliContext, InitContext, RpcContext};
|
||||||
use crate::db::model::public::ServerStatus;
|
use crate::db::model::public::ServerStatus;
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
use crate::disk::mount::util::unmount;
|
use crate::developer::OS_DEVELOPER_KEY_PATH;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::Hostname;
|
||||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
use crate::middleware::auth::AuthContext;
|
||||||
use crate::net::net_controller::{NetController, NetService};
|
use crate::net::net_controller::{NetController, NetService};
|
||||||
|
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::{UpgradableListener, WebServerAcceptorSetter};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -38,7 +34,7 @@ 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::{create_file, open_file, IOHook};
|
use crate::util::io::{open_file, IOHook};
|
||||||
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::{cpupower, Invoke};
|
||||||
@@ -167,28 +163,7 @@ pub async fn init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
local_auth.start();
|
local_auth.start();
|
||||||
tokio::fs::create_dir_all("/run/startos")
|
RpcContext::init_auth_cookie().await?;
|
||||||
.await
|
|
||||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, "mkdir -p /run/startos"))?;
|
|
||||||
if tokio::fs::metadata(LOCAL_AUTH_COOKIE_PATH).await.is_err() {
|
|
||||||
tokio::fs::write(
|
|
||||||
LOCAL_AUTH_COOKIE_PATH,
|
|
||||||
base64::encode(random::<[u8; 32]>()).as_bytes(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.with_ctx(|_| {
|
|
||||||
(
|
|
||||||
crate::ErrorKind::Filesystem,
|
|
||||||
format!("write {}", LOCAL_AUTH_COOKIE_PATH),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
tokio::fs::set_permissions(LOCAL_AUTH_COOKIE_PATH, Permissions::from_mode(0o046)).await?;
|
|
||||||
Command::new("chown")
|
|
||||||
.arg("root:startos")
|
|
||||||
.arg(LOCAL_AUTH_COOKIE_PATH)
|
|
||||||
.invoke(crate::ErrorKind::Filesystem)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
local_auth.complete();
|
local_auth.complete();
|
||||||
|
|
||||||
load_database.start();
|
load_database.start();
|
||||||
@@ -199,6 +174,16 @@ pub async fn init(
|
|||||||
load_database.complete();
|
load_database.complete();
|
||||||
|
|
||||||
load_ssh_keys.start();
|
load_ssh_keys.start();
|
||||||
|
crate::developer::write_developer_key(
|
||||||
|
&peek.as_private().as_developer_key().de()?.0,
|
||||||
|
OS_DEVELOPER_KEY_PATH,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Command::new("chown")
|
||||||
|
.arg("root:startos")
|
||||||
|
.arg(OS_DEVELOPER_KEY_PATH)
|
||||||
|
.invoke(ErrorKind::Filesystem)
|
||||||
|
.await?;
|
||||||
crate::ssh::sync_keys(
|
crate::ssh::sync_keys(
|
||||||
&Hostname(peek.as_public().as_server_info().as_hostname().de()?),
|
&Hostname(peek.as_public().as_server_info().as_hostname().de()?),
|
||||||
&peek.as_private().as_ssh_privkey().de()?,
|
&peek.as_private().as_ssh_privkey().de()?,
|
||||||
@@ -206,6 +191,13 @@ pub async fn init(
|
|||||||
SSH_DIR,
|
SSH_DIR,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
crate::ssh::sync_keys(
|
||||||
|
&Hostname(peek.as_public().as_server_info().as_hostname().de()?),
|
||||||
|
&peek.as_private().as_ssh_privkey().de()?,
|
||||||
|
&Default::default(),
|
||||||
|
"/root/.ssh",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
load_ssh_keys.complete();
|
load_ssh_keys.complete();
|
||||||
|
|
||||||
let account = AccountInfo::load(&peek)?;
|
let account = AccountInfo::load(&peek)?;
|
||||||
@@ -214,17 +206,12 @@ pub async fn init(
|
|||||||
let net_ctrl = Arc::new(
|
let net_ctrl = Arc::new(
|
||||||
NetController::init(
|
NetController::init(
|
||||||
db.clone(),
|
db.clone(),
|
||||||
cfg.tor_control
|
|
||||||
.unwrap_or(SocketAddr::from(([127, 0, 0, 1], 9051))),
|
|
||||||
cfg.tor_socks.unwrap_or(SocketAddr::V4(SocketAddrV4::new(
|
|
||||||
Ipv4Addr::new(127, 0, 0, 1),
|
|
||||||
9050,
|
|
||||||
))),
|
|
||||||
&account.hostname,
|
&account.hostname,
|
||||||
|
cfg.socks_listen.unwrap_or(DEFAULT_SOCKS_LISTEN),
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
);
|
);
|
||||||
webserver.try_upgrade(|a| net_ctrl.net_iface.upgrade_listener(a))?;
|
webserver.try_upgrade(|a| net_ctrl.net_iface.watcher.upgrade_listener(a))?;
|
||||||
let os_net_service = net_ctrl.os_bindings().await?;
|
let os_net_service = net_ctrl.os_bindings().await?;
|
||||||
start_net.complete();
|
start_net.complete();
|
||||||
|
|
||||||
@@ -260,7 +247,8 @@ pub async fn init(
|
|||||||
Command::new("killall")
|
Command::new("killall")
|
||||||
.arg("journalctl")
|
.arg("journalctl")
|
||||||
.invoke(crate::ErrorKind::Journald)
|
.invoke(crate::ErrorKind::Journald)
|
||||||
.await?;
|
.await
|
||||||
|
.log_err();
|
||||||
mount_logs.complete();
|
mount_logs.complete();
|
||||||
tokio::io::copy(
|
tokio::io::copy(
|
||||||
&mut open_file("/run/startos/init.log").await?,
|
&mut open_file("/run/startos/init.log").await?,
|
||||||
@@ -508,14 +496,7 @@ pub async fn init_progress(ctx: InitContext) -> Result<InitProgressRes, Error> {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Err(e) = ws
|
if let Err(e) = ws.close_result(res.map(|_| "complete")).await {
|
||||||
.close_result(res.map(|_| "complete").map_err(|e| {
|
|
||||||
tracing::error!("error in init progress websocket: {e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
e
|
|
||||||
}))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
tracing::error!("error closing init progress websocket: {e}");
|
tracing::error!("error closing init progress websocket: {e}");
|
||||||
tracing::debug!("{e:?}");
|
tracing::debug!("{e:?}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use axum::extract::ws;
|
use axum::extract::ws;
|
||||||
use clap::builder::ValueParserFactory;
|
use clap::builder::ValueParserFactory;
|
||||||
use clap::{value_parser, CommandFactory, FromArgMatches, 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::{AsyncWriteExt, StreamExt};
|
||||||
use imbl_value::{json, InternedString};
|
use imbl_value::{InternedString, json};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::{FromStrParser, VersionString};
|
use models::{FromStrParser, VersionString};
|
||||||
use reqwest::header::{HeaderMap, CONTENT_LENGTH};
|
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use reqwest::header::{CONTENT_LENGTH, HeaderMap};
|
||||||
use rpc_toolkit::HandlerArgs;
|
use rpc_toolkit::HandlerArgs;
|
||||||
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rustyline_async::ReadlineEvent;
|
use rustyline_async::ReadlineEvent;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
@@ -31,9 +31,9 @@ use crate::registry::package::get::GetPackageResponse;
|
|||||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::upload::upload;
|
use crate::upload::upload;
|
||||||
|
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::Never;
|
|
||||||
|
|
||||||
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";
|
||||||
@@ -253,7 +253,7 @@ pub async fn sideload(
|
|||||||
.await;
|
.await;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = async {
|
if let Err(e) = async {
|
||||||
let key = ctx.db.peek().await.into_private().into_compat_s9pk_key();
|
let key = ctx.db.peek().await.into_private().into_developer_key();
|
||||||
|
|
||||||
ctx.services
|
ctx.services
|
||||||
.install(
|
.install(
|
||||||
@@ -483,7 +483,9 @@ 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!("Multiple flavors of {id} found. Please select one of the following versions to install:");
|
println!(
|
||||||
|
"Multiple flavors of {id} found. Please select one of the following versions to install:"
|
||||||
|
);
|
||||||
let version;
|
let version;
|
||||||
loop {
|
loop {
|
||||||
let (mut read, mut output) = rustyline_async::Readline::new("> ".into())
|
let (mut read, mut output) = rustyline_async::Readline::new("> ".into())
|
||||||
|
|||||||
@@ -60,10 +60,12 @@ pub mod s9pk;
|
|||||||
pub mod service;
|
pub mod service;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
pub mod shutdown;
|
pub mod shutdown;
|
||||||
|
pub mod sign;
|
||||||
pub mod sound;
|
pub mod sound;
|
||||||
pub mod ssh;
|
pub mod ssh;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
pub mod system;
|
pub mod system;
|
||||||
|
pub mod tunnel;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
pub mod upload;
|
pub mod upload;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
@@ -77,8 +79,8 @@ pub use error::{Error, ErrorKind, ResultExt};
|
|||||||
use imbl_value::Value;
|
use imbl_value::Value;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{
|
use rpc_toolkit::{
|
||||||
from_fn, from_fn_async, from_fn_blocking, CallRemoteHandler, Context, Empty, HandlerExt,
|
CallRemoteHandler, Context, Empty, HandlerExt, ParentHandler, from_fn, from_fn_async,
|
||||||
ParentHandler,
|
from_fn_blocking,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -89,7 +91,7 @@ use crate::context::{
|
|||||||
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::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
|
use crate::util::serde::{HandlerExtSerde, WithIoFormat, display_serializable};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -148,13 +150,12 @@ pub fn main_api<C: Context>() -> ParentHandler<C> {
|
|||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"net",
|
"net",
|
||||||
net::net::<C>().with_about("Network commands related to tor and dhcp"),
|
net::net_api::<C>().with_about("Network commands related to tor and dhcp"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"auth",
|
"auth",
|
||||||
auth::auth::<C>().with_about(
|
auth::auth::<C, RpcContext>()
|
||||||
"Commands related to Authentication i.e. login, logout, reset-password",
|
.with_about("Commands related to Authentication i.e. login, logout"),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"db",
|
"db",
|
||||||
@@ -582,7 +583,7 @@ pub fn expanded_api() -> ParentHandler<CliContext> {
|
|||||||
main_api()
|
main_api()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"init",
|
"init",
|
||||||
from_fn_blocking(developer::init)
|
from_fn_async(developer::init)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Create developer key if it doesn't exist"),
|
.with_about("Create developer key if it doesn't exist"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -551,8 +551,8 @@ pub async fn journalctl(
|
|||||||
let deserialized_entries = String::from_utf8(cmd.invoke(ErrorKind::Journald).await?)?
|
let deserialized_entries = String::from_utf8(cmd.invoke(ErrorKind::Journald).await?)?
|
||||||
.lines()
|
.lines()
|
||||||
.map(serde_json::from_str::<JournalctlEntry>)
|
.map(serde_json::from_str::<JournalctlEntry>)
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.filter_map(|e| e.ok())
|
||||||
.with_kind(ErrorKind::Deserialization)?;
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if follow {
|
if follow {
|
||||||
let mut follow_cmd = gen_journalctl_command(&id);
|
let mut follow_cmd = gen_journalctl_command(&id);
|
||||||
@@ -573,11 +573,8 @@ pub async fn journalctl(
|
|||||||
|
|
||||||
let follow_deserialized_entries = journalctl_entries
|
let follow_deserialized_entries = journalctl_entries
|
||||||
.map_err(|e| Error::new(e, crate::ErrorKind::Journald))
|
.map_err(|e| Error::new(e, crate::ErrorKind::Journald))
|
||||||
.and_then(|s| {
|
.try_filter_map(|s| {
|
||||||
futures::future::ready(
|
futures::future::ready(Ok(serde_json::from_str::<JournalctlEntry>(&s).ok()))
|
||||||
serde_json::from_str::<JournalctlEntry>(&s)
|
|
||||||
.with_kind(crate::ErrorKind::Deserialization),
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let entries = futures::stream::iter(deserialized_entries)
|
let entries = futures::stream::iter(deserialized_entries)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ use crate::rpc_continuations::{Guid, RpcContinuation};
|
|||||||
use crate::service::ServiceStats;
|
use crate::service::ServiceStats;
|
||||||
use crate::util::io::open_file;
|
use crate::util::io::open_file;
|
||||||
use crate::util::rpc_client::UnixRpcClient;
|
use crate::util::rpc_client::UnixRpcClient;
|
||||||
use crate::util::{new_guid, Invoke};
|
use crate::util::{Invoke, new_guid};
|
||||||
|
|
||||||
const LXC_CONTAINER_DIR: &str = "/var/lib/lxc";
|
const LXC_CONTAINER_DIR: &str = "/var/lib/lxc";
|
||||||
const RPC_DIR: &str = "media/startos/rpc"; // must not be absolute path
|
const RPC_DIR: &str = "media/startos/rpc"; // must not be absolute path
|
||||||
|
|||||||
@@ -1,29 +1,155 @@
|
|||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
use std::future::Future;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use axum::extract::Request;
|
use axum::extract::Request;
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
|
use base64::Engine;
|
||||||
use basic_cookies::Cookie;
|
use basic_cookies::Cookie;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
use helpers::const_true;
|
use helpers::const_true;
|
||||||
use http::header::{COOKIE, USER_AGENT};
|
|
||||||
use http::HeaderValue;
|
use http::HeaderValue;
|
||||||
use imbl_value::InternedString;
|
use http::header::{COOKIE, USER_AGENT};
|
||||||
|
use imbl_value::{InternedString, json};
|
||||||
|
use rand::random;
|
||||||
use rpc_toolkit::yajrc::INTERNAL_ERROR;
|
use rpc_toolkit::yajrc::INTERNAL_ERROR;
|
||||||
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio::process::Command;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
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::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::rpc_continuations::OpenAuthedContinuations;
|
||||||
|
use crate::sign::AnyVerifyingKey;
|
||||||
|
use crate::util::Invoke;
|
||||||
|
use crate::util::io::{create_file_mod, read_file_to_string};
|
||||||
|
use crate::util::iter::TransposeResultIterExt;
|
||||||
|
use crate::util::serde::BASE64;
|
||||||
|
use crate::util::sync::SyncMutex;
|
||||||
|
|
||||||
pub const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie";
|
pub trait AuthContext: SignatureAuthContext {
|
||||||
|
const LOCAL_AUTH_COOKIE_PATH: &str;
|
||||||
|
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str;
|
||||||
|
fn init_auth_cookie() -> impl Future<Output = Result<(), Error>> + Send {
|
||||||
|
async {
|
||||||
|
let mut file = create_file_mod(Self::LOCAL_AUTH_COOKIE_PATH, 0o046).await?;
|
||||||
|
file.write_all(BASE64.encode(random::<[u8; 32]>()).as_bytes())
|
||||||
|
.await?;
|
||||||
|
file.sync_all().await?;
|
||||||
|
drop(file);
|
||||||
|
Command::new("chown")
|
||||||
|
.arg(Self::LOCAL_AUTH_COOKIE_OWNERSHIP)
|
||||||
|
.arg(Self::LOCAL_AUTH_COOKIE_PATH)
|
||||||
|
.invoke(crate::ErrorKind::Filesystem)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn ephemeral_sessions(&self) -> &SyncMutex<Sessions>;
|
||||||
|
fn open_authed_continuations(&self) -> &OpenAuthedContinuations<Option<InternedString>>;
|
||||||
|
fn access_sessions(db: &mut Model<Self::Database>) -> &mut Model<Sessions>;
|
||||||
|
fn check_password(db: &Model<Self::Database>, password: &str) -> Result<(), Error>;
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn post_login_hook(&self, password: &str) -> impl Future<Output = Result<(), Error>> + Send {
|
||||||
|
async { Ok(()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
const LOCAL_AUTH_COOKIE_PATH: &str = "/run/startos/rpc.authcookie";
|
||||||
|
const LOCAL_AUTH_COOKIE_OWNERSHIP: &str = "root:startos";
|
||||||
|
fn ephemeral_sessions(&self) -> &SyncMutex<Sessions> {
|
||||||
|
&self.ephemeral_sessions
|
||||||
|
}
|
||||||
|
fn open_authed_continuations(&self) -> &OpenAuthedContinuations<Option<InternedString>> {
|
||||||
|
&self.open_authed_continuations
|
||||||
|
}
|
||||||
|
fn access_sessions(db: &mut Model<Self::Database>) -> &mut Model<Sessions> {
|
||||||
|
db.as_private_mut().as_sessions_mut()
|
||||||
|
}
|
||||||
|
fn check_password(db: &Model<Self::Database>, password: &str) -> Result<(), Error> {
|
||||||
|
check_password(&db.as_private().as_password().de()?, password)
|
||||||
|
}
|
||||||
|
async fn post_login_hook(&self, password: &str) -> Result<(), Error> {
|
||||||
|
if tokio::fs::metadata("/media/startos/config/overlay/etc/shadow")
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
write_shadow(&password).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -40,25 +166,25 @@ pub trait AsLogoutSessionId {
|
|||||||
pub struct HasLoggedOutSessions(());
|
pub struct HasLoggedOutSessions(());
|
||||||
|
|
||||||
impl HasLoggedOutSessions {
|
impl HasLoggedOutSessions {
|
||||||
pub async fn new(
|
pub async fn new<C: AuthContext>(
|
||||||
sessions: impl IntoIterator<Item = impl AsLogoutSessionId>,
|
sessions: impl IntoIterator<Item = impl AsLogoutSessionId>,
|
||||||
ctx: &RpcContext,
|
ctx: &C,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let to_log_out: BTreeSet<_> = sessions
|
let to_log_out: BTreeSet<_> = sessions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.as_logout_session_id())
|
.map(|s| s.as_logout_session_id())
|
||||||
.collect();
|
.collect();
|
||||||
for sid in &to_log_out {
|
for sid in &to_log_out {
|
||||||
ctx.open_authed_continuations.kill(&Some(sid.clone()))
|
ctx.open_authed_continuations().kill(&Some(sid.clone()))
|
||||||
}
|
}
|
||||||
ctx.ephemeral_sessions.mutate(|s| {
|
ctx.ephemeral_sessions().mutate(|s| {
|
||||||
for sid in &to_log_out {
|
for sid in &to_log_out {
|
||||||
s.0.remove(sid);
|
s.0.remove(sid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ctx.db
|
ctx.db()
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
let sessions = db.as_private_mut().as_sessions_mut();
|
let sessions = C::access_sessions(db);
|
||||||
for sid in &to_log_out {
|
for sid in &to_log_out {
|
||||||
sessions.remove(sid)?;
|
sessions.remove(sid)?;
|
||||||
}
|
}
|
||||||
@@ -82,9 +208,9 @@ enum SessionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HasValidSession {
|
impl HasValidSession {
|
||||||
pub async fn from_header(
|
pub async fn from_header<C: AuthContext>(
|
||||||
header: Option<&HeaderValue>,
|
header: Option<&HeaderValue>,
|
||||||
ctx: &RpcContext,
|
ctx: &C,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
if let Some(cookie_header) = header {
|
if let Some(cookie_header) = header {
|
||||||
let cookies = Cookie::parse(
|
let cookies = Cookie::parse(
|
||||||
@@ -94,7 +220,7 @@ impl HasValidSession {
|
|||||||
)
|
)
|
||||||
.with_kind(crate::ErrorKind::Authorization)?;
|
.with_kind(crate::ErrorKind::Authorization)?;
|
||||||
if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "local") {
|
if let Some(cookie) = cookies.iter().find(|c| c.get_name() == "local") {
|
||||||
if let Ok(s) = Self::from_local(cookie).await {
|
if let Ok(s) = Self::from_local::<C>(cookie).await {
|
||||||
return Ok(s);
|
return Ok(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,12 +237,12 @@ impl HasValidSession {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_session(
|
pub async fn from_session<C: AuthContext>(
|
||||||
session_token: HashSessionToken,
|
session_token: HashSessionToken,
|
||||||
ctx: &RpcContext,
|
ctx: &C,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let session_hash = session_token.hashed();
|
let session_hash = session_token.hashed();
|
||||||
if !ctx.ephemeral_sessions.mutate(|s| {
|
if !ctx.ephemeral_sessions().mutate(|s| {
|
||||||
if let Some(session) = s.0.get_mut(session_hash) {
|
if let Some(session) = s.0.get_mut(session_hash) {
|
||||||
session.last_active = Utc::now();
|
session.last_active = Utc::now();
|
||||||
true
|
true
|
||||||
@@ -124,10 +250,9 @@ impl HasValidSession {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
ctx.db
|
ctx.db()
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
db.as_private_mut()
|
C::access_sessions(db)
|
||||||
.as_sessions_mut()
|
|
||||||
.as_idx_mut(session_hash)
|
.as_idx_mut(session_hash)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization)
|
Error::new(eyre!("UNAUTHORIZED"), crate::ErrorKind::Authorization)
|
||||||
@@ -143,8 +268,8 @@ impl HasValidSession {
|
|||||||
Ok(Self(SessionType::Session(session_token)))
|
Ok(Self(SessionType::Session(session_token)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_local(local: &Cookie<'_>) -> Result<Self, Error> {
|
pub async fn from_local<C: AuthContext>(local: &Cookie<'_>) -> Result<Self, Error> {
|
||||||
let token = tokio::fs::read_to_string(LOCAL_AUTH_COOKIE_PATH).await?;
|
let token = read_file_to_string(C::LOCAL_AUTH_COOKIE_PATH).await?;
|
||||||
if local.get_value() == &*token {
|
if local.get_value() == &*token {
|
||||||
Ok(Self(SessionType::Local))
|
Ok(Self(SessionType::Local))
|
||||||
} else {
|
} else {
|
||||||
@@ -258,6 +383,8 @@ pub struct Metadata {
|
|||||||
login: bool,
|
login: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
get_session: bool,
|
get_session: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
get_signer: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -267,6 +394,7 @@ pub struct Auth {
|
|||||||
is_login: bool,
|
is_login: bool,
|
||||||
set_cookie: Option<HeaderValue>,
|
set_cookie: Option<HeaderValue>,
|
||||||
user_agent: Option<HeaderValue>,
|
user_agent: Option<HeaderValue>,
|
||||||
|
signature_auth: SignatureAuth,
|
||||||
}
|
}
|
||||||
impl Auth {
|
impl Auth {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -276,62 +404,73 @@ impl Auth {
|
|||||||
is_login: false,
|
is_login: false,
|
||||||
set_cookie: None,
|
set_cookie: None,
|
||||||
user_agent: None,
|
user_agent: None,
|
||||||
|
signature_auth: SignatureAuth::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Middleware<RpcContext> for Auth {
|
impl<C: AuthContext> Middleware<C> for Auth {
|
||||||
type Metadata = Metadata;
|
type Metadata = Metadata;
|
||||||
async fn process_http_request(
|
async fn process_http_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &RpcContext,
|
context: &C,
|
||||||
request: &mut Request,
|
request: &mut Request,
|
||||||
) -> Result<(), Response> {
|
) -> Result<(), Response> {
|
||||||
self.cookie = request.headers_mut().remove(COOKIE);
|
self.cookie = request.headers_mut().remove(COOKIE);
|
||||||
self.user_agent = request.headers_mut().remove(USER_AGENT);
|
self.user_agent = request.headers_mut().remove(USER_AGENT);
|
||||||
|
self.signature_auth
|
||||||
|
.process_http_request(context, request)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn process_rpc_request(
|
async fn process_rpc_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &RpcContext,
|
context: &C,
|
||||||
metadata: Self::Metadata,
|
metadata: Self::Metadata,
|
||||||
request: &mut RpcRequest,
|
request: &mut RpcRequest,
|
||||||
) -> Result<(), RpcResponse> {
|
) -> Result<(), RpcResponse> {
|
||||||
|
async {
|
||||||
if metadata.login {
|
if metadata.login {
|
||||||
self.is_login = true;
|
self.is_login = true;
|
||||||
let guard = self.rate_limiter.lock().await;
|
let guard = self.rate_limiter.lock().await;
|
||||||
if guard.1.elapsed() < Duration::from_secs(20) && guard.0 >= 3 {
|
if guard.1.elapsed() < Duration::from_secs(20) && guard.0 >= 3 {
|
||||||
return Err(RpcResponse {
|
return Err(Error::new(
|
||||||
id: request.id.take(),
|
|
||||||
result: Err(Error::new(
|
|
||||||
eyre!("Please limit login attempts to 3 per 20 seconds."),
|
eyre!("Please limit login attempts to 3 per 20 seconds."),
|
||||||
crate::ErrorKind::RateLimited,
|
crate::ErrorKind::RateLimited,
|
||||||
)
|
));
|
||||||
.into()),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
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"] = Value::String(Arc::new(user_agent.to_owned()))
|
request.params["__auth_userAgent"] =
|
||||||
|
Value::String(Arc::new(user_agent.to_owned()))
|
||||||
// TODO: will this panic?
|
// TODO: will this panic?
|
||||||
}
|
}
|
||||||
} else if metadata.authenticated {
|
} else if metadata.authenticated {
|
||||||
match HasValidSession::from_header(self.cookie.as_ref(), &context).await {
|
if self
|
||||||
Err(e) => {
|
.signature_auth
|
||||||
return Err(RpcResponse {
|
.process_rpc_request(
|
||||||
id: request.id.take(),
|
context,
|
||||||
result: Err(e.into()),
|
from_value(json!({
|
||||||
})
|
"get_signer": metadata.get_signer
|
||||||
}
|
}))?,
|
||||||
Ok(HasValidSession(SessionType::Session(s))) if metadata.get_session => {
|
request,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
match HasValidSession::from_header(self.cookie.as_ref(), context).await? {
|
||||||
|
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()));
|
||||||
// TODO: will this panic?
|
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn process_rpc_response(&mut self, _: &RpcContext, response: &mut RpcResponse) {
|
.await
|
||||||
|
.map_err(|e| RpcResponse::from_result(Err(e)))
|
||||||
|
}
|
||||||
|
async fn process_rpc_response(&mut self, _: &C, response: &mut RpcResponse) {
|
||||||
if self.is_login {
|
if self.is_login {
|
||||||
let mut guard = self.rate_limiter.lock().await;
|
let mut guard = self.rate_limiter.lock().await;
|
||||||
if guard.1.elapsed() < Duration::from_secs(20) {
|
if guard.1.elapsed() < Duration::from_secs(20) {
|
||||||
@@ -349,7 +488,7 @@ impl Middleware<RpcContext> for Auth {
|
|||||||
let login_res = from_value::<LoginRes>(res.clone())?;
|
let login_res = from_value::<LoginRes>(res.clone())?;
|
||||||
self.set_cookie = Some(
|
self.set_cookie = Some(
|
||||||
HeaderValue::from_str(&format!(
|
HeaderValue::from_str(&format!(
|
||||||
"session={}; Path=/; SameSite=Lax; Expires=Fri, 31 Dec 9999 23:59:59 GMT;",
|
"session={}; Path=/; SameSite=Strict; Expires=Fri, 31 Dec 9999 23:59:59 GMT;",
|
||||||
login_res.session
|
login_res.session
|
||||||
))
|
))
|
||||||
.with_kind(crate::ErrorKind::Network)?,
|
.with_kind(crate::ErrorKind::Network)?,
|
||||||
@@ -361,7 +500,7 @@ impl Middleware<RpcContext> for Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn process_http_response(&mut self, _: &RpcContext, response: &mut Response) {
|
async fn process_http_response(&mut self, _: &C, response: &mut Response) {
|
||||||
if let Some(set_cookie) = self.set_cookie.take() {
|
if let Some(set_cookie) = self.set_cookie.take() {
|
||||||
response.headers_mut().insert("set-cookie", set_cookie);
|
response.headers_mut().insert("set-cookie", set_cookie);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
use http::header::InvalidHeaderValue;
|
|
||||||
use http::HeaderValue;
|
use http::HeaderValue;
|
||||||
|
use http::header::InvalidHeaderValue;
|
||||||
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod cors;
|
pub mod cors;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
pub mod signature;
|
||||||
|
|||||||
@@ -1,45 +1,62 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::future::Future;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
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 axum::response::Response;
|
|
||||||
use chrono::Utc;
|
|
||||||
use http::HeaderValue;
|
use http::HeaderValue;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{Middleware, RpcRequest, RpcResponse};
|
use rpc_toolkit::{Context, Middleware, RpcRequest, RpcResponse};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use tokio::io::AsyncWriteExt;
|
use serde::de::DeserializeOwned;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use ts_rs::TS;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::context::CliContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::sign::commitment::Commitment;
|
||||||
use crate::registry::signer::commitment::request::RequestCommitment;
|
use crate::sign::commitment::request::RequestCommitment;
|
||||||
use crate::registry::signer::commitment::Commitment;
|
use crate::sign::{AnySignature, AnySigningKey, AnyVerifyingKey, SignatureScheme};
|
||||||
use crate::registry::signer::sign::{
|
|
||||||
AnySignature, AnySigningKey, AnyVerifyingKey, SignatureScheme,
|
|
||||||
};
|
|
||||||
use crate::util::serde::Base64;
|
use crate::util::serde::Base64;
|
||||||
|
|
||||||
pub const AUTH_SIG_HEADER: &str = "X-StartOS-Registry-Auth-Sig";
|
pub trait SignatureAuthContext: Context {
|
||||||
|
type Database: HasModel<Model = Model<Self::Database>> + Send + Sync;
|
||||||
|
type AdditionalMetadata: DeserializeOwned + Send;
|
||||||
|
type CheckPubkeyRes: Send;
|
||||||
|
fn db(&self) -> &TypedPatchDb<Self::Database>;
|
||||||
|
fn sig_context(
|
||||||
|
&self,
|
||||||
|
) -> impl Future<Output = impl IntoIterator<Item = Result<impl AsRef<str> + Send, Error>> + Send>
|
||||||
|
+ Send;
|
||||||
|
fn check_pubkey(
|
||||||
|
db: &Model<Self::Database>,
|
||||||
|
pubkey: Option<&AnyVerifyingKey>,
|
||||||
|
metadata: Self::AdditionalMetadata,
|
||||||
|
) -> Result<Self::CheckPubkeyRes, Error>;
|
||||||
|
fn post_auth_hook(
|
||||||
|
&self,
|
||||||
|
check_pubkey_res: Self::CheckPubkeyRes,
|
||||||
|
request: &RpcRequest,
|
||||||
|
) -> impl Future<Output = Result<(), Error>> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const AUTH_SIG_HEADER: &str = "X-StartOS-Auth-Sig";
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct Metadata {
|
pub struct Metadata<Additional> {
|
||||||
#[serde(default)]
|
#[serde(flatten)]
|
||||||
admin: bool,
|
additional: Additional,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
get_signer: bool,
|
get_signer: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Auth {
|
pub struct SignatureAuth {
|
||||||
nonce_cache: Arc<Mutex<BTreeMap<Instant, u64>>>, // for replay protection
|
nonce_cache: Arc<Mutex<BTreeMap<Instant, u64>>>, // for replay protection
|
||||||
signer: Option<Result<AnyVerifyingKey, RpcError>>,
|
signer: Option<Result<AnyVerifyingKey, RpcError>>,
|
||||||
}
|
}
|
||||||
impl Auth {
|
impl SignatureAuth {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
nonce_cache: Arc::new(Mutex::new(BTreeMap::new())),
|
nonce_cache: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
@@ -65,15 +82,6 @@ impl Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, TS)]
|
|
||||||
pub struct RegistryAdminLogRecord {
|
|
||||||
pub timestamp: String,
|
|
||||||
pub name: String,
|
|
||||||
#[ts(type = "{ id: string | number | null; method: string; params: any }")]
|
|
||||||
pub request: RpcRequest,
|
|
||||||
pub key: AnyVerifyingKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SignatureHeader {
|
pub struct SignatureHeader {
|
||||||
pub commitment: RequestCommitment,
|
pub commitment: RequestCommitment,
|
||||||
pub signer: AnyVerifyingKey,
|
pub signer: AnyVerifyingKey,
|
||||||
@@ -120,13 +128,13 @@ impl SignatureHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Middleware<RegistryContext> for Auth {
|
impl<C: SignatureAuthContext> Middleware<C> for SignatureAuth {
|
||||||
type Metadata = Metadata;
|
type Metadata = Metadata<C::AdditionalMetadata>;
|
||||||
async fn process_http_request(
|
async fn process_http_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &RegistryContext,
|
context: &C,
|
||||||
request: &mut Request,
|
request: &mut Request,
|
||||||
) -> Result<(), Response> {
|
) -> Result<(), axum::response::Response> {
|
||||||
if request.headers().contains_key(AUTH_SIG_HEADER) {
|
if request.headers().contains_key(AUTH_SIG_HEADER) {
|
||||||
self.signer = Some(
|
self.signer = Some(
|
||||||
async {
|
async {
|
||||||
@@ -138,15 +146,27 @@ impl Middleware<RegistryContext> for Auth {
|
|||||||
request
|
request
|
||||||
.headers()
|
.headers()
|
||||||
.get(AUTH_SIG_HEADER)
|
.get(AUTH_SIG_HEADER)
|
||||||
.or_not_found("missing X-StartOS-Registry-Auth-Sig")
|
.or_not_found(AUTH_SIG_HEADER)
|
||||||
.with_kind(ErrorKind::InvalidRequest)?,
|
.with_kind(ErrorKind::InvalidRequest)?,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
context.sig_context().await.into_iter().fold(
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("no valid signature context available to verify"),
|
||||||
|
ErrorKind::Authorization,
|
||||||
|
)),
|
||||||
|
|acc, x| {
|
||||||
|
if acc.is_ok() {
|
||||||
|
acc
|
||||||
|
} else {
|
||||||
signer.scheme().verify_commitment(
|
signer.scheme().verify_commitment(
|
||||||
&signer,
|
&signer,
|
||||||
&commitment,
|
&commitment,
|
||||||
&ctx.hostname,
|
x?.as_ref(),
|
||||||
&signature,
|
&signature,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let now = SystemTime::now()
|
let now = SystemTime::now()
|
||||||
@@ -175,48 +195,83 @@ impl Middleware<RegistryContext> for Auth {
|
|||||||
}
|
}
|
||||||
async fn process_rpc_request(
|
async fn process_rpc_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &RegistryContext,
|
context: &C,
|
||||||
metadata: Self::Metadata,
|
metadata: Self::Metadata,
|
||||||
request: &mut RpcRequest,
|
request: &mut RpcRequest,
|
||||||
) -> Result<(), RpcResponse> {
|
) -> Result<(), RpcResponse> {
|
||||||
async move {
|
async {
|
||||||
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)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if metadata.admin {
|
let db = context.db().peek().await;
|
||||||
let signer = signer
|
let res = C::check_pubkey(&db, signer.as_ref(), metadata.additional)?;
|
||||||
.ok_or_else(|| Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization))?;
|
context.post_auth_hook(res, request).await?;
|
||||||
let db = ctx.db.peek().await;
|
|
||||||
let (guid, admin) = db.as_index().as_signers().get_signer_info(&signer)?;
|
|
||||||
if db.into_admins().de()?.contains(&guid) {
|
|
||||||
let mut log = tokio::fs::OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open(ctx.datadir.join("admin.log"))
|
|
||||||
.await?;
|
|
||||||
log.write_all(
|
|
||||||
(serde_json::to_string(&RegistryAdminLogRecord {
|
|
||||||
timestamp: Utc::now().to_rfc3339(),
|
|
||||||
name: admin.name,
|
|
||||||
request: request.clone(),
|
|
||||||
key: signer,
|
|
||||||
})
|
|
||||||
.with_kind(ErrorKind::Serialization)?
|
|
||||||
+ "\n")
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
.await
|
.await
|
||||||
.map_err(|e| RpcResponse::from_result(Err(e)))
|
.map_err(|e: Error| rpc_toolkit::RpcResponse::from_result(Err(e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn call_remote(
|
||||||
|
ctx: &CliContext,
|
||||||
|
url: Url,
|
||||||
|
sig_context: &str,
|
||||||
|
method: &str,
|
||||||
|
params: Value,
|
||||||
|
) -> Result<Value, RpcError> {
|
||||||
|
use reqwest::Method;
|
||||||
|
use reqwest::header::{ACCEPT, CONTENT_LENGTH, CONTENT_TYPE};
|
||||||
|
use rpc_toolkit::RpcResponse;
|
||||||
|
use rpc_toolkit::yajrc::{GenericRpcMethod, Id, RpcRequest};
|
||||||
|
|
||||||
|
let rpc_req = RpcRequest {
|
||||||
|
id: Some(Id::Number(0.into())),
|
||||||
|
method: GenericRpcMethod::<_, _, Value>::new(method),
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
let body = serde_json::to_vec(&rpc_req)?;
|
||||||
|
let mut req = ctx
|
||||||
|
.client
|
||||||
|
.request(Method::POST, url)
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.header(ACCEPT, "application/json")
|
||||||
|
.header(CONTENT_LENGTH, body.len());
|
||||||
|
if let Ok(key) = ctx.developer_key() {
|
||||||
|
req = req.header(
|
||||||
|
AUTH_SIG_HEADER,
|
||||||
|
SignatureHeader::sign(&AnySigningKey::Ed25519(key.clone()), &body, sig_context)?
|
||||||
|
.to_header(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let res = req.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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,21 +2,21 @@ use std::collections::{BTreeMap, BTreeSet};
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use async_acme::acme::Identifier;
|
use async_acme::acme::Identifier;
|
||||||
use clap::builder::ValueParserFactory;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use clap::builder::ValueParserFactory;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::{ErrorData, FromStrParser};
|
use models::{ErrorData, FromStrParser};
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
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 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::public::AcmeSettings;
|
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
|
use crate::db::model::public::AcmeSettings;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::{Pem, Pkcs8Doc};
|
use crate::util::serde::{Pem, Pkcs8Doc};
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ impl<'a> async_acme::cache::AcmeCache for AcmeCertCache<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn acme<C: Context>() -> ParentHandler<C> {
|
pub fn acme_api<C: Context>() -> ParentHandler<C> {
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"init",
|
"init",
|
||||||
@@ -257,7 +257,8 @@ pub async fn init(
|
|||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
db.as_public_mut()
|
db.as_public_mut()
|
||||||
.as_server_info_mut().as_network_mut()
|
.as_server_info_mut()
|
||||||
|
.as_network_mut()
|
||||||
.as_acme_mut()
|
.as_acme_mut()
|
||||||
.insert(&provider, &AcmeSettings { contact })
|
.insert(&provider, &AcmeSettings { contact })
|
||||||
})
|
})
|
||||||
@@ -279,7 +280,8 @@ pub async fn remove(
|
|||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
db.as_public_mut()
|
db.as_public_mut()
|
||||||
.as_server_info_mut().as_network_mut()
|
.as_server_info_mut()
|
||||||
|
.as_network_mut()
|
||||||
.as_acme_mut()
|
.as_acme_mut()
|
||||||
.remove(&provider)
|
.remove(&provider)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,42 +1,304 @@
|
|||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
|
use futures::future::BoxFuture;
|
||||||
|
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
use models::PackageId;
|
use hickory_client::client::Client;
|
||||||
|
use hickory_client::proto::runtime::TokioRuntimeProvider;
|
||||||
|
use hickory_client::proto::tcp::TcpClientStream;
|
||||||
|
use hickory_client::proto::udp::UdpClientStream;
|
||||||
|
use hickory_client::proto::xfer::{DnsExchangeBackground, DnsRequestOptions};
|
||||||
|
use hickory_client::proto::DnsHandle;
|
||||||
|
use hickory_server::authority::MessageResponseBuilder;
|
||||||
|
use hickory_server::proto::op::{Header, ResponseCode};
|
||||||
|
use hickory_server::proto::rr::{Name, Record, RecordType};
|
||||||
|
use hickory_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
|
||||||
|
use hickory_server::ServerFuture;
|
||||||
|
use imbl::OrdMap;
|
||||||
|
use imbl_value::InternedString;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use models::{GatewayId, OptionExt, PackageId};
|
||||||
|
use rpc_toolkit::{
|
||||||
|
from_fn_async, from_fn_blocking, Context, HandlerArgs, HandlerExt, ParentHandler,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::net::{TcpListener, UdpSocket};
|
use tokio::net::{TcpListener, UdpSocket};
|
||||||
use tokio::process::Command;
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use trust_dns_server::authority::MessageResponseBuilder;
|
|
||||||
use trust_dns_server::proto::op::{Header, ResponseCode};
|
|
||||||
use trust_dns_server::proto::rr::{Name, Record, RecordType};
|
|
||||||
use trust_dns_server::server::{Request, RequestHandler, ResponseHandler, ResponseInfo};
|
|
||||||
use trust_dns_server::ServerFuture;
|
|
||||||
|
|
||||||
use crate::net::forward::START9_BRIDGE_IFACE;
|
use crate::context::RpcContext;
|
||||||
use crate::util::sync::Watch;
|
use crate::db::model::public::NetworkInterfaceInfo;
|
||||||
use crate::util::Invoke;
|
use crate::db::model::Database;
|
||||||
use crate::{Error, ErrorKind, ResultExt};
|
use crate::net::gateway::NetworkInterfaceWatcher;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::util::io::file_string_stream;
|
||||||
|
use crate::util::serde::{display_serializable, HandlerExtSerde};
|
||||||
|
use crate::util::sync::{SyncRwLock, Watch};
|
||||||
|
|
||||||
|
pub fn dns_api<C: Context>() -> ParentHandler<C> {
|
||||||
|
ParentHandler::new()
|
||||||
|
.subcommand(
|
||||||
|
"query",
|
||||||
|
from_fn_blocking(query_dns::<C>)
|
||||||
|
.with_display_serializable()
|
||||||
|
.with_custom_display_fn(|HandlerArgs { params, .. }, res| {
|
||||||
|
if let Some(format) = params.format {
|
||||||
|
return display_serializable(format, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ip) = res {
|
||||||
|
println!("{}", ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.with_about("Test the DNS configuration for a domain"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"set-static",
|
||||||
|
from_fn_async(set_static_dns)
|
||||||
|
.no_display()
|
||||||
|
.with_about("Set static DNS servers"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
|
pub struct QueryDnsParams {
|
||||||
|
pub fqdn: InternedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query_dns<C: Context>(
|
||||||
|
_: C,
|
||||||
|
QueryDnsParams { fqdn }: QueryDnsParams,
|
||||||
|
) -> Result<Option<Ipv4Addr>, Error> {
|
||||||
|
let hints = dns_lookup::AddrInfoHints {
|
||||||
|
flags: 0,
|
||||||
|
address: libc::AF_INET,
|
||||||
|
socktype: 0,
|
||||||
|
protocol: 0,
|
||||||
|
};
|
||||||
|
dns_lookup::getaddrinfo(Some(&*fqdn), None, Some(hints))
|
||||||
|
.map(Some)
|
||||||
|
.or_else(|e| {
|
||||||
|
if matches!(
|
||||||
|
e.kind(),
|
||||||
|
dns_lookup::LookupErrorKind::NoName | dns_lookup::LookupErrorKind::NoData
|
||||||
|
) {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Err(std::io::Error::from(e))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with_kind(ErrorKind::Network)?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.find_map(|a| match a.map(|a| a.sockaddr.ip()) {
|
||||||
|
Ok(IpAddr::V4(a)) => Some(Ok(a)),
|
||||||
|
Err(e) => Some(Err(e)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
.map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
|
pub struct SetStaticDnsParams {
|
||||||
|
pub servers: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_static_dns(
|
||||||
|
ctx: RpcContext,
|
||||||
|
SetStaticDnsParams { servers }: SetStaticDnsParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
db.as_public_mut()
|
||||||
|
.as_server_info_mut()
|
||||||
|
.as_network_mut()
|
||||||
|
.as_dns_mut()
|
||||||
|
.as_static_servers_mut()
|
||||||
|
.ser(
|
||||||
|
&servers
|
||||||
|
.map(|s| {
|
||||||
|
s.into_iter()
|
||||||
|
.map(|s| {
|
||||||
|
s.parse::<SocketAddr>()
|
||||||
|
.or_else(|_| s.parse::<IpAddr>().map(|a| (a, 53).into()))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.transpose()?,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ResolveMap {
|
||||||
|
private_domains: BTreeMap<InternedString, Weak<()>>,
|
||||||
|
services: BTreeMap<Option<PackageId>, BTreeMap<Ipv4Addr, Weak<()>>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DnsController {
|
pub struct DnsController {
|
||||||
services: Weak<RwLock<BTreeMap<Option<PackageId>, BTreeMap<Ipv4Addr, Weak<()>>>>>,
|
resolve: Weak<SyncRwLock<ResolveMap>>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
dns_server: NonDetachingJoinHandle<Result<(), Error>>,
|
dns_server: NonDetachingJoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DnsClient {
|
||||||
|
client: Arc<SyncRwLock<Vec<(SocketAddr, hickory_client::client::Client)>>>,
|
||||||
|
_thread: NonDetachingJoinHandle<()>,
|
||||||
|
}
|
||||||
|
impl DnsClient {
|
||||||
|
pub fn new(db: TypedPatchDb<Database>) -> Self {
|
||||||
|
let client = Arc::new(SyncRwLock::new(Vec::new()));
|
||||||
|
Self {
|
||||||
|
client: client.clone(),
|
||||||
|
_thread: tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
if let Err::<(), Error>(e) = async {
|
||||||
|
let mut stream = file_string_stream("/run/systemd/resolve/resolv.conf")
|
||||||
|
.filter_map(|a| futures::future::ready(a.transpose()))
|
||||||
|
.boxed();
|
||||||
|
let mut conf: String = stream
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.or_not_found("/run/systemd/resolve/resolv.conf")??;
|
||||||
|
let mut prev_nameservers = Vec::new();
|
||||||
|
let mut bg = BTreeMap::<SocketAddr, BoxFuture<_>>::new();
|
||||||
|
loop {
|
||||||
|
let nameservers = conf
|
||||||
|
.lines()
|
||||||
|
.map(|l| l.trim())
|
||||||
|
.filter_map(|l| l.strip_prefix("nameserver "))
|
||||||
|
.skip(2)
|
||||||
|
.map(|n| {
|
||||||
|
n.parse::<SocketAddr>()
|
||||||
|
.or_else(|_| n.parse::<IpAddr>().map(|a| (a, 53).into()))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
let static_nameservers = db
|
||||||
|
.mutate(|db| {
|
||||||
|
let dns = db
|
||||||
|
.as_public_mut()
|
||||||
|
.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());
|
||||||
|
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.boxed());
|
||||||
|
client
|
||||||
|
};
|
||||||
|
new.push((*addr, client));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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")??,
|
||||||
|
_ = futures::future::join(
|
||||||
|
futures::future::join_all(bg.values_mut()),
|
||||||
|
futures::future::pending::<()>(),
|
||||||
|
) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("{e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn lookup(
|
||||||
|
&self,
|
||||||
|
query: hickory_client::proto::op::Query,
|
||||||
|
options: DnsRequestOptions,
|
||||||
|
) -> Vec<hickory_client::proto::xfer::DnsExchangeSend> {
|
||||||
|
self.client.peek(|c| {
|
||||||
|
c.iter()
|
||||||
|
.map(|(_, c)| c.lookup(query.clone(), options.clone()))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Resolver {
|
struct Resolver {
|
||||||
services: Arc<RwLock<BTreeMap<Option<PackageId>, BTreeMap<Ipv4Addr, Weak<()>>>>>,
|
client: DnsClient,
|
||||||
|
net_iface: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>,
|
||||||
|
resolve: Arc<SyncRwLock<ResolveMap>>,
|
||||||
}
|
}
|
||||||
impl Resolver {
|
impl Resolver {
|
||||||
async fn resolve(&self, name: &Name) -> Option<Vec<Ipv4Addr>> {
|
fn resolve(&self, name: &Name, src: IpAddr) -> Option<Vec<IpAddr>> {
|
||||||
|
self.resolve.peek(|r| {
|
||||||
|
if r.private_domains
|
||||||
|
.get(&*name.to_lowercase().to_ascii())
|
||||||
|
.map_or(false, |d| d.strong_count() > 0)
|
||||||
|
{
|
||||||
|
if let Some(res) = self.net_iface.peek(|i| {
|
||||||
|
i.values()
|
||||||
|
.chain([NetworkInterfaceInfo::lxc_bridge().1])
|
||||||
|
.flat_map(|i| i.ip_info.as_ref())
|
||||||
|
.find(|i| i.subnets.iter().any(|s| s.contains(&src)))
|
||||||
|
.map(|ip_info| {
|
||||||
|
let mut res = ip_info.subnets.iter().collect::<Vec<_>>();
|
||||||
|
res.sort_by_cached_key(|a| !a.contains(&src));
|
||||||
|
res.into_iter().map(|s| s.addr()).collect()
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
return Some(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
match name.iter().next_back() {
|
match name.iter().next_back() {
|
||||||
Some(b"embassy") | Some(b"startos") => {
|
Some(b"embassy") | Some(b"startos") => {
|
||||||
if let Some(pkg) = name.iter().rev().skip(1).next() {
|
if let Some(pkg) = name.iter().rev().skip(1).next() {
|
||||||
if let Some(ip) = self.services.read().await.get(&Some(
|
if let Some(ip) = r.services.get(&Some(
|
||||||
std::str::from_utf8(pkg)
|
std::str::from_utf8(pkg)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.parse()
|
.parse()
|
||||||
@@ -45,17 +307,17 @@ impl Resolver {
|
|||||||
Some(
|
Some(
|
||||||
ip.iter()
|
ip.iter()
|
||||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||||
.map(|(ip, _)| *ip)
|
.map(|(ip, _)| (*ip).into())
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
} else if let Some(ip) = self.services.read().await.get(&None) {
|
} else if let Some(ip) = r.services.get(&None) {
|
||||||
Some(
|
Some(
|
||||||
ip.iter()
|
ip.iter()
|
||||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||||
.map(|(ip, _)| *ip)
|
.map(|(ip, _)| (*ip).into())
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -64,6 +326,7 @@ impl Resolver {
|
|||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,20 +337,61 @@ impl RequestHandler for Resolver {
|
|||||||
request: &Request,
|
request: &Request,
|
||||||
mut response_handle: R,
|
mut response_handle: R,
|
||||||
) -> ResponseInfo {
|
) -> ResponseInfo {
|
||||||
let query = request.request_info().query;
|
match async {
|
||||||
if let Some(ip) = self.resolve(query.name().borrow()).await {
|
let req = request.request_info()?;
|
||||||
|
let query = req.query;
|
||||||
|
if let Some(ip) = self.resolve(query.name().borrow(), req.src.ip()) {
|
||||||
match query.query_type() {
|
match query.query_type() {
|
||||||
RecordType::A => {
|
RecordType::A => {
|
||||||
|
let mut header = Header::response_from_request(request.header());
|
||||||
|
header.set_recursion_available(true);
|
||||||
response_handle
|
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,
|
||||||
&ip.into_iter()
|
&ip.into_iter()
|
||||||
|
.filter_map(|a| {
|
||||||
|
if let IpAddr::V4(a) = a {
|
||||||
|
Some(a)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.map(|ip| {
|
.map(|ip| {
|
||||||
Record::from_rdata(
|
Record::from_rdata(
|
||||||
request.request_info().query.name().to_owned().into(),
|
query.name().to_owned().into(),
|
||||||
0,
|
0,
|
||||||
trust_dns_server::proto::rr::RData::A(ip.into()),
|
hickory_server::proto::rr::RData::A(ip.into()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
RecordType::AAAA => {
|
||||||
|
let mut header = Header::response_from_request(request.header());
|
||||||
|
header.set_recursion_available(true);
|
||||||
|
response_handle
|
||||||
|
.send_response(
|
||||||
|
MessageResponseBuilder::from_message_request(&*request).build(
|
||||||
|
header,
|
||||||
|
&ip.into_iter()
|
||||||
|
.filter_map(|a| {
|
||||||
|
if let IpAddr::V6(a) = a {
|
||||||
|
Some(a)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|ip| {
|
||||||
|
Record::from_rdata(
|
||||||
|
query.name().to_owned().into(),
|
||||||
|
0,
|
||||||
|
hickory_server::proto::rr::RData::AAAA(ip.into()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
@@ -99,11 +403,12 @@ impl RequestHandler for Resolver {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let res = Header::response_from_request(request.header());
|
let mut header = Header::response_from_request(request.header());
|
||||||
|
header.set_recursion_available(true);
|
||||||
response_handle
|
response_handle
|
||||||
.send_response(
|
.send_response(
|
||||||
MessageResponseBuilder::from_message_request(&*request).build(
|
MessageResponseBuilder::from_message_request(&*request).build(
|
||||||
res.into(),
|
header.into(),
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
@@ -114,12 +419,39 @@ impl RequestHandler for Resolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut res = Header::response_from_request(request.header());
|
let query = query.original().clone();
|
||||||
res.set_response_code(ResponseCode::NXDomain);
|
let mut streams = self.client.lookup(query, DnsRequestOptions::default());
|
||||||
|
let mut err = None;
|
||||||
|
for stream in streams.iter_mut() {
|
||||||
|
match tokio::time::timeout(Duration::from_secs(5), stream.next()).await {
|
||||||
|
Ok(Some(Err(e))) => err = Some(e),
|
||||||
|
Ok(Some(Ok(msg))) => {
|
||||||
|
return response_handle
|
||||||
|
.send_response(
|
||||||
|
MessageResponseBuilder::from_message_request(&*request).build(
|
||||||
|
Header::response_from_request(request.header()),
|
||||||
|
msg.answers(),
|
||||||
|
msg.name_servers(),
|
||||||
|
&msg.soa().map(|s| s.to_owned().into_record_of_rdata()),
|
||||||
|
msg.additionals(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(e) = err {
|
||||||
|
tracing::error!("{e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
}
|
||||||
|
let mut header = Header::response_from_request(request.header());
|
||||||
|
header.set_recursion_available(true);
|
||||||
|
header.set_response_code(ResponseCode::ServFail);
|
||||||
response_handle
|
response_handle
|
||||||
.send_response(
|
.send_response(
|
||||||
MessageResponseBuilder::from_message_request(&*request).build(
|
MessageResponseBuilder::from_message_request(&*request).build(
|
||||||
res.into(),
|
header,
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
@@ -128,78 +460,92 @@ impl RequestHandler for Resolver {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
.unwrap_or_else(|e| {
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => {
|
||||||
tracing::error!("{}", e);
|
tracing::error!("{}", e);
|
||||||
tracing::debug!("{:?}", e);
|
tracing::debug!("{:?}", e);
|
||||||
let mut res = Header::response_from_request(request.header());
|
let mut header = Header::response_from_request(request.header());
|
||||||
res.set_response_code(ResponseCode::ServFail);
|
header.set_recursion_available(true);
|
||||||
res.into()
|
header.set_response_code(ResponseCode::ServFail);
|
||||||
})
|
response_handle
|
||||||
|
.send_response(
|
||||||
|
MessageResponseBuilder::from_message_request(&*request).build(
|
||||||
|
header,
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_or(header.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DnsController {
|
impl DnsController {
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn init(mut lxcbr_status: Watch<bool>) -> Result<Self, Error> {
|
pub async fn init(
|
||||||
let services = Arc::new(RwLock::new(BTreeMap::new()));
|
db: TypedPatchDb<Database>,
|
||||||
|
watcher: &NetworkInterfaceWatcher,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let resolve = Arc::new(SyncRwLock::new(ResolveMap::default()));
|
||||||
|
|
||||||
let mut server = ServerFuture::new(Resolver {
|
let mut server = ServerFuture::new(Resolver {
|
||||||
services: services.clone(),
|
client: DnsClient::new(db),
|
||||||
|
net_iface: watcher.subscribe(),
|
||||||
|
resolve: resolve.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let dns_server = tokio::spawn(async move {
|
let dns_server = tokio::spawn(
|
||||||
|
async move {
|
||||||
server.register_listener(
|
server.register_listener(
|
||||||
TcpListener::bind((Ipv4Addr::LOCALHOST, 53))
|
TcpListener::bind((Ipv6Addr::UNSPECIFIED, 53))
|
||||||
.await
|
.await
|
||||||
.with_kind(ErrorKind::Network)?,
|
.with_kind(ErrorKind::Network)?,
|
||||||
Duration::from_secs(30),
|
Duration::from_secs(30),
|
||||||
);
|
);
|
||||||
server.register_socket(
|
server.register_socket(
|
||||||
UdpSocket::bind((Ipv4Addr::LOCALHOST, 53))
|
UdpSocket::bind((Ipv6Addr::UNSPECIFIED, 53))
|
||||||
.await
|
.await
|
||||||
.with_kind(ErrorKind::Network)?,
|
.with_kind(ErrorKind::Network)?,
|
||||||
);
|
);
|
||||||
|
|
||||||
lxcbr_status.wait_for(|a| *a).await;
|
|
||||||
|
|
||||||
Command::new("resolvectl")
|
|
||||||
.arg("dns")
|
|
||||||
.arg(START9_BRIDGE_IFACE)
|
|
||||||
.arg("127.0.0.1")
|
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await?;
|
|
||||||
Command::new("resolvectl")
|
|
||||||
.arg("domain")
|
|
||||||
.arg(START9_BRIDGE_IFACE)
|
|
||||||
.arg("embassy")
|
|
||||||
.invoke(ErrorKind::Network)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
server
|
server
|
||||||
.block_until_done()
|
.block_until_done()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Error::new(e, ErrorKind::Network))
|
.with_kind(ErrorKind::Network)
|
||||||
})
|
}
|
||||||
|
.map(|r| {
|
||||||
|
r.log_err();
|
||||||
|
}),
|
||||||
|
)
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
services: Arc::downgrade(&services),
|
resolve: Arc::downgrade(&resolve),
|
||||||
dns_server,
|
dns_server,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add(&self, pkg_id: Option<PackageId>, ip: Ipv4Addr) -> Result<Arc<()>, Error> {
|
pub fn add_service(&self, pkg_id: Option<PackageId>, ip: Ipv4Addr) -> Result<Arc<()>, Error> {
|
||||||
if let Some(services) = Weak::upgrade(&self.services) {
|
if let Some(resolve) = Weak::upgrade(&self.resolve) {
|
||||||
let mut writable = services.write().await;
|
resolve.mutate(|writable| {
|
||||||
let mut ips = writable.remove(&pkg_id).unwrap_or_default();
|
let ips = writable.services.entry(pkg_id).or_default();
|
||||||
let rc = if let Some(rc) = Weak::upgrade(&ips.remove(&ip).unwrap_or_default()) {
|
let weak = ips.entry(ip).or_default();
|
||||||
|
let rc = if let Some(rc) = Weak::upgrade(&*weak) {
|
||||||
rc
|
rc
|
||||||
} else {
|
} else {
|
||||||
Arc::new(())
|
let new = Arc::new(());
|
||||||
|
*weak = Arc::downgrade(&new);
|
||||||
|
new
|
||||||
};
|
};
|
||||||
ips.insert(ip, Arc::downgrade(&rc));
|
|
||||||
writable.insert(pkg_id, ips);
|
|
||||||
Ok(rc)
|
Ok(rc)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new(
|
Err(Error::new(
|
||||||
eyre!("DNS Server Thread has exited"),
|
eyre!("DNS Server Thread has exited"),
|
||||||
@@ -208,17 +554,65 @@ impl DnsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn gc(&self, pkg_id: Option<PackageId>, ip: Ipv4Addr) -> Result<(), Error> {
|
pub fn gc_service(&self, pkg_id: Option<PackageId>, ip: Ipv4Addr) -> Result<(), Error> {
|
||||||
if let Some(services) = Weak::upgrade(&self.services) {
|
if let Some(resolve) = Weak::upgrade(&self.resolve) {
|
||||||
let mut writable = services.write().await;
|
resolve.mutate(|writable| {
|
||||||
let mut ips = writable.remove(&pkg_id).unwrap_or_default();
|
let mut ips = writable.services.remove(&pkg_id).unwrap_or_default();
|
||||||
if let Some(rc) = Weak::upgrade(&ips.remove(&ip).unwrap_or_default()) {
|
if let Some(rc) = Weak::upgrade(&ips.remove(&ip).unwrap_or_default()) {
|
||||||
ips.insert(ip, Arc::downgrade(&rc));
|
ips.insert(ip, Arc::downgrade(&rc));
|
||||||
}
|
}
|
||||||
if !ips.is_empty() {
|
if !ips.is_empty() {
|
||||||
writable.insert(pkg_id, ips);
|
writable.services.insert(pkg_id, ips);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("DNS Server Thread has exited"),
|
||||||
|
crate::ErrorKind::Network,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_private_domain(&self, fqdn: InternedString) -> Result<Arc<()>, Error> {
|
||||||
|
if let Some(resolve) = Weak::upgrade(&self.resolve) {
|
||||||
|
resolve.mutate(|writable| {
|
||||||
|
let weak = writable.private_domains.entry(fqdn).or_default();
|
||||||
|
let rc = if let Some(rc) = Weak::upgrade(&*weak) {
|
||||||
|
rc
|
||||||
|
} else {
|
||||||
|
let new = Arc::new(());
|
||||||
|
*weak = Arc::downgrade(&new);
|
||||||
|
new
|
||||||
|
};
|
||||||
|
Ok(rc)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
eyre!("DNS Server Thread has exited"),
|
||||||
|
crate::ErrorKind::Network,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gc_private_domains<'a, BK: Ord + 'a>(
|
||||||
|
&self,
|
||||||
|
domains: impl IntoIterator<Item = &'a BK> + 'a,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
InternedString: Borrow<BK>,
|
||||||
|
{
|
||||||
|
if let Some(resolve) = Weak::upgrade(&self.resolve) {
|
||||||
|
resolve.mutate(|writable| {
|
||||||
|
for domain in domains {
|
||||||
|
if let Some((k, v)) = writable.private_domains.remove_entry(domain) {
|
||||||
|
if v.strong_count() > 0 {
|
||||||
|
writable.private_domains.insert(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new(
|
Err(Error::new(
|
||||||
eyre!("DNS Server Thread has exited"),
|
eyre!("DNS Server Thread has exited"),
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr, SocketAddrV6};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
use id_pool::IdPool;
|
use id_pool::IdPool;
|
||||||
use imbl_value::InternedString;
|
use imbl::OrdMap;
|
||||||
|
use models::GatewayId;
|
||||||
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::db::model::public::NetworkInterfaceInfo;
|
use crate::db::model::public::NetworkInterfaceInfo;
|
||||||
|
use crate::net::gateway::{DynInterfaceFilter, InterfaceFilter};
|
||||||
|
use crate::net::utils::ipv6_is_link_local;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::sync::Watch;
|
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
|
use crate::util::sync::Watch;
|
||||||
|
|
||||||
pub const START9_BRIDGE_IFACE: &str = "lxcbr0";
|
pub const START9_BRIDGE_IFACE: &str = "lxcbr0";
|
||||||
pub const FIRST_DYNAMIC_PRIVATE_PORT: u16 = 49152;
|
pub const FIRST_DYNAMIC_PRIVATE_PORT: u16 = 49152;
|
||||||
@@ -39,106 +42,162 @@ impl AvailablePorts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ForwardRequest {
|
struct ForwardRequest {
|
||||||
public: bool,
|
external: u16,
|
||||||
target: SocketAddr,
|
target: SocketAddr,
|
||||||
|
filter: DynInterfaceFilter,
|
||||||
rc: Weak<()>,
|
rc: Weak<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
struct ForwardEntry {
|
||||||
|
external: u16,
|
||||||
|
target: SocketAddr,
|
||||||
|
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 {
|
||||||
|
Self {
|
||||||
|
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,
|
||||||
|
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
|
filter: Option<DynInterfaceFilter>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if self.rc.strong_count() == 0 {
|
||||||
|
return self.take().destroy().await;
|
||||||
|
}
|
||||||
|
let filter_ref = filter.as_ref().unwrap_or(&self.prev_filter);
|
||||||
|
let mut keep = BTreeSet::<SocketAddr>::new();
|
||||||
|
for (iface, info) in ip_info
|
||||||
|
.iter()
|
||||||
|
.chain([NetworkInterfaceInfo::loopback()])
|
||||||
|
.filter(|(id, info)| filter_ref.filter(*id, *info))
|
||||||
|
{
|
||||||
|
if let Some(ip_info) = &info.ip_info {
|
||||||
|
for ipnet in &ip_info.subnets {
|
||||||
|
let addr = match ipnet.addr() {
|
||||||
|
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
|
||||||
|
.keys()
|
||||||
|
.copied()
|
||||||
|
.filter(|a| !keep.contains(a))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for rm in rm {
|
||||||
|
if let Some((source, interface)) = self.forwards.remove_entry(&rm) {
|
||||||
|
unforward(interface.as_str(), source, self.target).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(filter) = filter {
|
||||||
|
self.prev_filter = filter;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_request(
|
||||||
|
&mut self,
|
||||||
|
ForwardRequest {
|
||||||
|
external,
|
||||||
|
target,
|
||||||
|
filter,
|
||||||
|
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 {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.forwards.is_empty() {
|
||||||
|
let take = self.take();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
take.destroy().await.log_err();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
struct ForwardState {
|
struct ForwardState {
|
||||||
requested: BTreeMap<u16, ForwardRequest>,
|
state: BTreeMap<u16, ForwardEntry>,
|
||||||
current: BTreeMap<u16, BTreeMap<InternedString, SocketAddr>>,
|
|
||||||
}
|
}
|
||||||
impl ForwardState {
|
impl ForwardState {
|
||||||
|
async fn handle_request(
|
||||||
|
&mut self,
|
||||||
|
request: ForwardRequest,
|
||||||
|
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.state
|
||||||
|
.entry(request.external)
|
||||||
|
.or_insert_with(|| ForwardEntry::new(request.external, request.target, Weak::new()))
|
||||||
|
.update_request(request, ip_info)
|
||||||
|
.await
|
||||||
|
}
|
||||||
async fn sync(
|
async fn sync(
|
||||||
&mut self,
|
&mut self,
|
||||||
interfaces: &BTreeMap<InternedString, (bool, Vec<Ipv4Addr>)>,
|
ip_info: &OrdMap<GatewayId, NetworkInterfaceInfo>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let private_interfaces = interfaces
|
for entry in self.state.values_mut() {
|
||||||
.iter()
|
entry.update(ip_info, None).await?;
|
||||||
.filter(|(_, (public, _))| !*public)
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
.collect::<BTreeSet<_>>();
|
|
||||||
let all_interfaces = interfaces.keys().collect::<BTreeSet<_>>();
|
|
||||||
self.requested.retain(|_, req| req.rc.strong_count() > 0);
|
|
||||||
for external in self
|
|
||||||
.requested
|
|
||||||
.keys()
|
|
||||||
.chain(self.current.keys())
|
|
||||||
.copied()
|
|
||||||
.collect::<BTreeSet<_>>()
|
|
||||||
{
|
|
||||||
match (
|
|
||||||
self.requested.get(&external),
|
|
||||||
self.current.get_mut(&external),
|
|
||||||
) {
|
|
||||||
(Some(req), Some(cur)) => {
|
|
||||||
let expected = if req.public {
|
|
||||||
&all_interfaces
|
|
||||||
} else {
|
|
||||||
&private_interfaces
|
|
||||||
};
|
|
||||||
let actual = cur.keys().collect::<BTreeSet<_>>();
|
|
||||||
let mut to_rm = actual
|
|
||||||
.difference(expected)
|
|
||||||
.copied()
|
|
||||||
.map(|i| (i.clone(), &interfaces[i].1))
|
|
||||||
.collect::<BTreeMap<_, _>>();
|
|
||||||
let mut to_add = expected
|
|
||||||
.difference(&actual)
|
|
||||||
.copied()
|
|
||||||
.map(|i| (i.clone(), &interfaces[i].1))
|
|
||||||
.collect::<BTreeMap<_, _>>();
|
|
||||||
for interface in actual.intersection(expected).copied() {
|
|
||||||
if cur[interface] != req.target {
|
|
||||||
to_rm.insert(interface.clone(), &interfaces[interface].1);
|
|
||||||
to_add.insert(interface.clone(), &interfaces[interface].1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (interface, ips) in to_rm {
|
|
||||||
for ip in ips {
|
|
||||||
unforward(&*interface, (*ip, external).into(), cur[&interface]).await?;
|
|
||||||
}
|
|
||||||
cur.remove(&interface);
|
|
||||||
}
|
|
||||||
for (interface, ips) in to_add {
|
|
||||||
cur.insert(interface.clone(), req.target);
|
|
||||||
for ip in ips {
|
|
||||||
forward(&*interface, (*ip, external).into(), cur[&interface]).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Some(req), None) => {
|
|
||||||
let cur = self.current.entry(external).or_default();
|
|
||||||
for interface in if req.public {
|
|
||||||
&all_interfaces
|
|
||||||
} else {
|
|
||||||
&private_interfaces
|
|
||||||
}
|
|
||||||
.into_iter()
|
|
||||||
.copied()
|
|
||||||
{
|
|
||||||
cur.insert(interface.clone(), req.target);
|
|
||||||
for ip in &interfaces[interface].1 {
|
|
||||||
forward(&**interface, (*ip, external).into(), req.target).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(None, Some(cur)) => {
|
|
||||||
let to_rm = cur.keys().cloned().collect::<BTreeSet<_>>();
|
|
||||||
for interface in to_rm {
|
|
||||||
for ip in &interfaces[&interface].1 {
|
|
||||||
unforward(&*interface, (*ip, external).into(), cur[&interface]).await?;
|
|
||||||
}
|
|
||||||
cur.remove(&interface);
|
|
||||||
}
|
|
||||||
self.current.remove(&external);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
self.state.retain(|_, fwd| !fwd.forwards.is_empty());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,86 +209,36 @@ fn err_has_exited<T>(_: T) -> Error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LanPortForwardController {
|
pub struct PortForwardController {
|
||||||
req: mpsc::UnboundedSender<(
|
req: mpsc::UnboundedSender<(Option<ForwardRequest>, oneshot::Sender<Result<(), Error>>)>,
|
||||||
Option<(u16, ForwardRequest)>,
|
|
||||||
oneshot::Sender<Result<(), Error>>,
|
|
||||||
)>,
|
|
||||||
_thread: NonDetachingJoinHandle<()>,
|
_thread: NonDetachingJoinHandle<()>,
|
||||||
}
|
}
|
||||||
impl LanPortForwardController {
|
impl PortForwardController {
|
||||||
pub fn new(mut ip_info: Watch<BTreeMap<InternedString, NetworkInterfaceInfo>>) -> Self {
|
pub fn new(mut ip_info: Watch<OrdMap<GatewayId, NetworkInterfaceInfo>>) -> Self {
|
||||||
let (req_send, mut req_recv) = mpsc::unbounded_channel();
|
let (req_send, mut req_recv) = mpsc::unbounded_channel::<(
|
||||||
|
Option<ForwardRequest>,
|
||||||
|
oneshot::Sender<Result<(), Error>>,
|
||||||
|
)>();
|
||||||
let thread = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
let thread = NonDetachingJoinHandle::from(tokio::spawn(async move {
|
||||||
let mut state = ForwardState::default();
|
let mut state = ForwardState::default();
|
||||||
let mut interfaces = ip_info.peek_and_mark_seen(|ip_info| {
|
let mut interfaces = ip_info.read_and_mark_seen();
|
||||||
ip_info
|
|
||||||
.iter()
|
|
||||||
.map(|(iface, info)| {
|
|
||||||
(
|
|
||||||
iface.clone(),
|
|
||||||
(
|
|
||||||
info.inbound(),
|
|
||||||
info.ip_info.as_ref().map_or(Vec::new(), |i| {
|
|
||||||
i.subnets
|
|
||||||
.iter()
|
|
||||||
.filter_map(|s| {
|
|
||||||
if let IpAddr::V4(ip) = s.addr() {
|
|
||||||
Some(ip)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
let mut reply: Option<oneshot::Sender<Result<(), Error>>> = None;
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
msg = req_recv.recv() => {
|
msg = req_recv.recv() => {
|
||||||
if let Some((msg, re)) = msg {
|
if let Some((msg, re)) = msg {
|
||||||
if let Some((external, req)) = msg {
|
if let Some(req) = msg {
|
||||||
state.requested.insert(external, req);
|
re.send(state.handle_request(req, &interfaces).await).ok();
|
||||||
|
} else {
|
||||||
|
re.send(state.sync(&interfaces).await).ok();
|
||||||
}
|
}
|
||||||
reply = Some(re);
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = ip_info.changed() => {
|
_ = ip_info.changed() => {
|
||||||
interfaces = ip_info.peek(|ip_info| {
|
interfaces = ip_info.read();
|
||||||
ip_info
|
state.sync(&interfaces).await.log_err();
|
||||||
.iter()
|
|
||||||
.map(|(iface, info)| (iface.clone(), (
|
|
||||||
info.inbound(),
|
|
||||||
info.ip_info.as_ref().map_or(Vec::new(), |i| {
|
|
||||||
i.subnets
|
|
||||||
.iter()
|
|
||||||
.filter_map(|s| {
|
|
||||||
if let IpAddr::V4(ip) = s.addr() {
|
|
||||||
Some(ip)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}),
|
|
||||||
)))
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let res = state.sync(&interfaces).await;
|
|
||||||
if let Err(e) = &res {
|
|
||||||
tracing::error!("Error in PortForwardController: {e}");
|
|
||||||
tracing::debug!("{e:?}");
|
|
||||||
}
|
|
||||||
if let Some(re) = reply.take() {
|
|
||||||
let _ = re.send(res);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -238,19 +247,22 @@ impl LanPortForwardController {
|
|||||||
_thread: thread,
|
_thread: thread,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn add(&self, port: u16, public: bool, target: SocketAddr) -> Result<Arc<()>, Error> {
|
pub async fn add(
|
||||||
|
&self,
|
||||||
|
external: u16,
|
||||||
|
filter: impl InterfaceFilter,
|
||||||
|
target: SocketAddr,
|
||||||
|
) -> 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((
|
||||||
Some((
|
Some(ForwardRequest {
|
||||||
port,
|
external,
|
||||||
ForwardRequest {
|
|
||||||
public,
|
|
||||||
target,
|
target,
|
||||||
|
filter: filter.into_dyn(),
|
||||||
rc: Arc::downgrade(&rc),
|
rc: Arc::downgrade(&rc),
|
||||||
},
|
}),
|
||||||
)),
|
|
||||||
send,
|
send,
|
||||||
))
|
))
|
||||||
.map_err(err_has_exited)?;
|
.map_err(err_has_exited)?;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,47 +1,46 @@
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
|
use models::GatewayId;
|
||||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use torut::onion::OnionAddressV3;
|
|
||||||
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::{all_hosts, HostApiKind};
|
||||||
|
use crate::net::tor::OnionAddress;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::{display_serializable, HandlerExtSerde};
|
use crate::util::serde::{display_serializable, HandlerExtSerde};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
#[serde(rename_all_fields = "camelCase")]
|
#[serde(rename_all_fields = "camelCase")]
|
||||||
#[serde(tag = "kind")]
|
#[serde(tag = "kind")]
|
||||||
#[ts(export)]
|
|
||||||
pub enum HostAddress {
|
pub enum HostAddress {
|
||||||
Onion {
|
Onion {
|
||||||
#[ts(type = "string")]
|
address: OnionAddress,
|
||||||
address: OnionAddressV3,
|
|
||||||
},
|
},
|
||||||
Domain {
|
Domain {
|
||||||
#[ts(type = "string")]
|
|
||||||
address: InternedString,
|
address: InternedString,
|
||||||
public: bool,
|
public: Option<PublicDomainConfig>,
|
||||||
acme: Option<AcmeProvider>,
|
private: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
||||||
pub struct DomainConfig {
|
pub struct PublicDomainConfig {
|
||||||
pub public: bool,
|
pub gateway: GatewayId,
|
||||||
pub acme: Option<AcmeProvider>,
|
pub acme: Option<AcmeProvider>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_duplicates(db: &DatabaseModel) -> Result<(), Error> {
|
fn handle_duplicates(db: &mut DatabaseModel) -> Result<(), Error> {
|
||||||
let mut onions = BTreeSet::<OnionAddressV3>::new();
|
let mut onions = BTreeSet::<OnionAddress>::new();
|
||||||
let mut domains = BTreeSet::<InternedString>::new();
|
let mut domains = BTreeSet::<InternedString>::new();
|
||||||
let mut check_onion = |onion: OnionAddressV3| {
|
let check_onion = |onions: &mut BTreeSet<OnionAddress>, onion: OnionAddress| {
|
||||||
if onions.contains(&onion) {
|
if onions.contains(&onion) {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("onion address {onion} is already in use"),
|
eyre!("onion address {onion} is already in use"),
|
||||||
@@ -51,7 +50,7 @@ fn check_duplicates(db: &DatabaseModel) -> Result<(), Error> {
|
|||||||
onions.insert(onion);
|
onions.insert(onion);
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
let mut check_domain = |domain: InternedString| {
|
let check_domain = |domains: &mut BTreeSet<InternedString>, domain: InternedString| {
|
||||||
if domains.contains(&domain) {
|
if domains.contains(&domain) {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("domain {domain} is already in use"),
|
eyre!("domain {domain} is already in use"),
|
||||||
@@ -61,13 +60,46 @@ fn check_duplicates(db: &DatabaseModel) -> Result<(), Error> {
|
|||||||
domains.insert(domain);
|
domains.insert(domain);
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
let mut not_in_use = Vec::new();
|
||||||
for host in all_hosts(db) {
|
for host in all_hosts(db) {
|
||||||
let host = host?;
|
let host = host?;
|
||||||
for onion in host.as_onions().de()? {
|
let in_use = host.as_bindings().de()?.values().any(|v| v.enabled);
|
||||||
check_onion(onion)?;
|
if !in_use {
|
||||||
|
not_in_use.push(host);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for onion in host.as_onions().de()? {
|
||||||
|
check_onion(&mut onions, onion)?;
|
||||||
|
}
|
||||||
|
let public = host.as_public_domains().keys()?;
|
||||||
|
for domain in &public {
|
||||||
|
check_domain(&mut domains, domain.clone())?;
|
||||||
|
}
|
||||||
|
for domain in host.as_private_domains().de()? {
|
||||||
|
if !public.contains(&domain) {
|
||||||
|
check_domain(&mut domains, domain)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for host in not_in_use {
|
||||||
|
host.as_onions_mut()
|
||||||
|
.mutate(|o| Ok(o.retain(|o| !onions.contains(o))))?;
|
||||||
|
host.as_public_domains_mut()
|
||||||
|
.mutate(|d| Ok(d.retain(|d, _| !domains.contains(d))))?;
|
||||||
|
host.as_private_domains_mut()
|
||||||
|
.mutate(|d| Ok(d.retain(|d| !domains.contains(d))))?;
|
||||||
|
|
||||||
|
for onion in host.as_onions().de()? {
|
||||||
|
check_onion(&mut onions, onion)?;
|
||||||
|
}
|
||||||
|
let public = host.as_public_domains().keys()?;
|
||||||
|
for domain in &public {
|
||||||
|
check_domain(&mut domains, domain.clone())?;
|
||||||
|
}
|
||||||
|
for domain in host.as_private_domains().de()? {
|
||||||
|
if !public.contains(&domain) {
|
||||||
|
check_domain(&mut domains, domain)?;
|
||||||
}
|
}
|
||||||
for domain in host.as_domains().keys()? {
|
|
||||||
check_domain(domain)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -78,25 +110,53 @@ pub fn address_api<C: Context, Kind: HostApiKind>(
|
|||||||
ParentHandler::<C, Kind::Params, Kind::InheritedParams>::new()
|
ParentHandler::<C, Kind::Params, Kind::InheritedParams>::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"domain",
|
"domain",
|
||||||
|
ParentHandler::<C, Empty, Kind::Inheritance>::new()
|
||||||
|
.subcommand(
|
||||||
|
"public",
|
||||||
ParentHandler::<C, Empty, Kind::Inheritance>::new()
|
ParentHandler::<C, Empty, Kind::Inheritance>::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"add",
|
"add",
|
||||||
from_fn_async(add_domain::<Kind>)
|
from_fn_async(add_public_domain::<Kind>)
|
||||||
.with_metadata("sync_db", Value::Bool(true))
|
.with_metadata("sync_db", Value::Bool(true))
|
||||||
.with_inherited(|_, a| a)
|
.with_inherited(|_, a| a)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Add an address to this host")
|
.with_about("Add a public domain to this host")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"remove",
|
"remove",
|
||||||
from_fn_async(remove_domain::<Kind>)
|
from_fn_async(remove_public_domain::<Kind>)
|
||||||
.with_metadata("sync_db", Value::Bool(true))
|
.with_metadata("sync_db", Value::Bool(true))
|
||||||
.with_inherited(|_, a| a)
|
.with_inherited(|_, a| a)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Remove an address from this host")
|
.with_about("Remove a public domain from this host")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
|
.with_inherited(|_, a| a),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"private",
|
||||||
|
ParentHandler::<C, Empty, Kind::Inheritance>::new()
|
||||||
|
.subcommand(
|
||||||
|
"add",
|
||||||
|
from_fn_async(add_private_domain::<Kind>)
|
||||||
|
.with_metadata("sync_db", Value::Bool(true))
|
||||||
|
.with_inherited(|_, a| a)
|
||||||
|
.no_display()
|
||||||
|
.with_about("Add a private domain to this host")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"remove",
|
||||||
|
from_fn_async(remove_private_domain::<Kind>)
|
||||||
|
.with_metadata("sync_db", Value::Bool(true))
|
||||||
|
.with_inherited(|_, a| a)
|
||||||
|
.no_display()
|
||||||
|
.with_about("Remove a private domain from this host")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
|
.with_inherited(|_, a| a),
|
||||||
|
)
|
||||||
.with_inherited(Kind::inheritance),
|
.with_inherited(Kind::inheritance),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
@@ -131,7 +191,7 @@ pub fn address_api<C: Context, Kind: HostApiKind>(
|
|||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
display_serializable(format, res);
|
display_serializable(format, res)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,15 +204,25 @@ pub fn address_api<C: Context, Kind: HostApiKind>(
|
|||||||
}
|
}
|
||||||
HostAddress::Domain {
|
HostAddress::Domain {
|
||||||
address,
|
address,
|
||||||
public,
|
public: Some(PublicDomainConfig { gateway, acme }),
|
||||||
acme,
|
private,
|
||||||
} => {
|
} => {
|
||||||
table.add_row(row![
|
table.add_row(row![
|
||||||
address,
|
address,
|
||||||
*public,
|
&format!(
|
||||||
|
"{} ({gateway})",
|
||||||
|
if *private { "YES" } else { "ONLY" }
|
||||||
|
),
|
||||||
acme.as_ref().map(|a| a.0.as_str()).unwrap_or("NONE")
|
acme.as_ref().map(|a| a.0.as_str()).unwrap_or("NONE")
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
HostAddress::Domain {
|
||||||
|
address,
|
||||||
|
public: None,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
table.add_row(row![address, &format!("NO"), "N/A"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,63 +236,109 @@ pub fn address_api<C: Context, Kind: HostApiKind>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser)]
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
pub struct AddDomainParams {
|
pub struct AddPublicDomainParams {
|
||||||
pub domain: InternedString,
|
pub fqdn: InternedString,
|
||||||
#[arg(long)]
|
|
||||||
pub private: bool,
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub acme: Option<AcmeProvider>,
|
pub acme: Option<AcmeProvider>,
|
||||||
|
pub gateway: GatewayId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_domain<Kind: HostApiKind>(
|
pub async fn add_public_domain<Kind: HostApiKind>(
|
||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
AddDomainParams {
|
AddPublicDomainParams {
|
||||||
domain,
|
fqdn,
|
||||||
private,
|
|
||||||
acme,
|
acme,
|
||||||
}: AddDomainParams,
|
gateway,
|
||||||
|
}: AddPublicDomainParams,
|
||||||
inheritance: Kind::Inheritance,
|
inheritance: Kind::Inheritance,
|
||||||
) -> Result<(), Error> {
|
) -> Result<Option<Ipv4Addr>, Error> {
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
if let Some(acme) = &acme {
|
if let Some(acme) = &acme {
|
||||||
if !db.as_public().as_server_info().as_network().as_acme().contains_key(&acme)? {
|
if !db
|
||||||
|
.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_acme()
|
||||||
|
.contains_key(&acme)?
|
||||||
|
{
|
||||||
return Err(Error::new(eyre!("unknown acme provider {}, please run acme.init for this provider first", acme.0), ErrorKind::InvalidRequest));
|
return Err(Error::new(eyre!("unknown acme provider {}, please run acme.init for this provider first", acme.0), ErrorKind::InvalidRequest));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kind::host_for(&inheritance, db)?
|
Kind::host_for(&inheritance, db)?
|
||||||
.as_domains_mut()
|
.as_public_domains_mut()
|
||||||
.insert(
|
.insert(&fqdn, &PublicDomainConfig { acme, gateway })?;
|
||||||
&domain,
|
handle_duplicates(db)
|
||||||
&DomainConfig {
|
|
||||||
public: !private,
|
|
||||||
acme,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
check_duplicates(db)
|
|
||||||
})
|
})
|
||||||
.await.result?;
|
.await
|
||||||
|
.result?;
|
||||||
|
Kind::sync_host(&ctx, inheritance).await?;
|
||||||
|
|
||||||
|
tokio::task::spawn_blocking(|| {
|
||||||
|
crate::net::dns::query_dns(ctx, crate::net::dns::QueryDnsParams { fqdn })
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Unknown)?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
|
pub struct RemoveDomainParams {
|
||||||
|
pub fqdn: InternedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove_public_domain<Kind: HostApiKind>(
|
||||||
|
ctx: RpcContext,
|
||||||
|
RemoveDomainParams { fqdn }: RemoveDomainParams,
|
||||||
|
inheritance: Kind::Inheritance,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
Kind::host_for(&inheritance, db)?
|
||||||
|
.as_public_domains_mut()
|
||||||
|
.remove(&fqdn)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
Kind::sync_host(&ctx, inheritance).await?;
|
Kind::sync_host(&ctx, inheritance).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser)]
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
pub struct RemoveDomainParams {
|
pub struct AddPrivateDomainParams {
|
||||||
pub domain: InternedString,
|
pub fqdn: InternedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_domain<Kind: HostApiKind>(
|
pub async fn add_private_domain<Kind: HostApiKind>(
|
||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
RemoveDomainParams { domain }: RemoveDomainParams,
|
AddPrivateDomainParams { fqdn }: AddPrivateDomainParams,
|
||||||
inheritance: Kind::Inheritance,
|
inheritance: Kind::Inheritance,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
Kind::host_for(&inheritance, db)?
|
Kind::host_for(&inheritance, db)?
|
||||||
.as_domains_mut()
|
.as_private_domains_mut()
|
||||||
.remove(&domain)
|
.mutate(|d| Ok(d.insert(fqdn)))?;
|
||||||
|
handle_duplicates(db)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
Kind::sync_host(&ctx, inheritance).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove_private_domain<Kind: HostApiKind>(
|
||||||
|
ctx: RpcContext,
|
||||||
|
RemoveDomainParams { fqdn: domain }: RemoveDomainParams,
|
||||||
|
inheritance: Kind::Inheritance,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
Kind::host_for(&inheritance, db)?
|
||||||
|
.as_private_domains_mut()
|
||||||
|
.mutate(|d| Ok(d.remove(&domain)))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
@@ -249,7 +365,7 @@ pub async fn add_onion<Kind: HostApiKind>(
|
|||||||
ErrorKind::InvalidOnionAddress,
|
ErrorKind::InvalidOnionAddress,
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
.parse::<OnionAddressV3>()?;
|
.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)?;
|
||||||
@@ -257,7 +373,7 @@ pub async fn add_onion<Kind: HostApiKind>(
|
|||||||
Kind::host_for(&inheritance, db)?
|
Kind::host_for(&inheritance, db)?
|
||||||
.as_onions_mut()
|
.as_onions_mut()
|
||||||
.mutate(|a| Ok(a.insert(onion)))?;
|
.mutate(|a| Ok(a.insert(onion)))?;
|
||||||
check_duplicates(db)
|
handle_duplicates(db)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
@@ -280,7 +396,7 @@ pub async fn remove_onion<Kind: HostApiKind>(
|
|||||||
ErrorKind::InvalidOnionAddress,
|
ErrorKind::InvalidOnionAddress,
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
.parse::<OnionAddressV3>()?;
|
.parse::<OnionAddress>()?;
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
Kind::host_for(&inheritance, db)?
|
Kind::host_for(&inheritance, db)?
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use clap::builder::ValueParserFactory;
|
use clap::builder::ValueParserFactory;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use models::{FromStrParser, HostId};
|
use imbl::OrdSet;
|
||||||
|
use models::{FromStrParser, GatewayId, HostId};
|
||||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerArgs, HandlerExt, ParentHandler};
|
||||||
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::public::NetworkInterfaceInfo;
|
||||||
use crate::net::forward::AvailablePorts;
|
use crate::net::forward::AvailablePorts;
|
||||||
|
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::*;
|
||||||
@@ -50,11 +53,16 @@ pub struct BindInfo {
|
|||||||
pub net: NetInfo,
|
pub net: NetInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, TS, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct NetInfo {
|
pub struct NetInfo {
|
||||||
pub public: bool,
|
#[ts(as = "BTreeSet::<GatewayId>")]
|
||||||
|
#[serde(default)]
|
||||||
|
pub private_disabled: OrdSet<GatewayId>,
|
||||||
|
#[ts(as = "BTreeSet::<GatewayId>")]
|
||||||
|
#[serde(default)]
|
||||||
|
pub public_enabled: OrdSet<GatewayId>,
|
||||||
pub assigned_port: Option<u16>,
|
pub assigned_port: Option<u16>,
|
||||||
pub assigned_ssl_port: Option<u16>,
|
pub assigned_ssl_port: Option<u16>,
|
||||||
}
|
}
|
||||||
@@ -65,16 +73,19 @@ impl BindInfo {
|
|||||||
if options.add_ssl.is_some() {
|
if options.add_ssl.is_some() {
|
||||||
assigned_ssl_port = Some(available_ports.alloc()?);
|
assigned_ssl_port = Some(available_ports.alloc()?);
|
||||||
}
|
}
|
||||||
if let Some(secure) = options.secure {
|
if options
|
||||||
if !secure.ssl || !options.add_ssl.is_some() {
|
.secure
|
||||||
|
.map_or(true, |s| !(s.ssl && options.add_ssl.is_some()))
|
||||||
|
{
|
||||||
assigned_port = Some(available_ports.alloc()?);
|
assigned_port = Some(available_ports.alloc()?);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
options,
|
options,
|
||||||
net: NetInfo {
|
net: NetInfo {
|
||||||
public: false,
|
private_disabled: OrdSet::new(),
|
||||||
|
public_enabled: OrdSet::new(),
|
||||||
assigned_port,
|
assigned_port,
|
||||||
assigned_ssl_port,
|
assigned_ssl_port,
|
||||||
},
|
},
|
||||||
@@ -88,7 +99,7 @@ impl BindInfo {
|
|||||||
let Self { net: mut lan, .. } = self;
|
let Self { net: mut lan, .. } = self;
|
||||||
if options
|
if options
|
||||||
.secure
|
.secure
|
||||||
.map_or(false, |s| !(s.ssl && options.add_ssl.is_some()))
|
.map_or(true, |s| !(s.ssl && options.add_ssl.is_some()))
|
||||||
// doesn't make sense to have 2 listening ports, both with ssl
|
// doesn't make sense to have 2 listening ports, both with ssl
|
||||||
{
|
{
|
||||||
lan.assigned_port = if let Some(port) = lan.assigned_port.take() {
|
lan.assigned_port = if let Some(port) = lan.assigned_port.take() {
|
||||||
@@ -122,6 +133,15 @@ impl BindInfo {
|
|||||||
self.enabled = false;
|
self.enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl InterfaceFilter for NetInfo {
|
||||||
|
fn filter(&self, id: &GatewayId, info: &NetworkInterfaceInfo) -> bool {
|
||||||
|
if info.public() {
|
||||||
|
self.public_enabled.contains(id)
|
||||||
|
} else {
|
||||||
|
!self.private_disabled.contains(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, TS)]
|
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@@ -165,12 +185,11 @@ pub fn binding<C: Context, Kind: HostApiKind>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
table.add_row(row![bc => "INTERNAL PORT", "ENABLED", "PUBLIC", "EXTERNAL PORT", "EXTERNAL SSL PORT"]);
|
table.add_row(row![bc => "INTERNAL PORT", "ENABLED", "EXTERNAL PORT", "EXTERNAL SSL PORT"]);
|
||||||
for (internal, info) in res {
|
for (internal, info) in res {
|
||||||
table.add_row(row![
|
table.add_row(row![
|
||||||
internal,
|
internal,
|
||||||
info.enabled,
|
info.enabled,
|
||||||
info.net.public,
|
|
||||||
if let Some(port) = info.net.assigned_port {
|
if let Some(port) = info.net.assigned_port {
|
||||||
port.to_string()
|
port.to_string()
|
||||||
} else {
|
} else {
|
||||||
@@ -192,12 +211,12 @@ pub fn binding<C: Context, Kind: HostApiKind>(
|
|||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"set-public",
|
"set-gateway-enabled",
|
||||||
from_fn_async(set_public::<Kind>)
|
from_fn_async(set_gateway_enabled::<Kind>)
|
||||||
.with_metadata("sync_db", Value::Bool(true))
|
.with_metadata("sync_db", Value::Bool(true))
|
||||||
.with_inherited(Kind::inheritance)
|
.with_inherited(Kind::inheritance)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Add an binding to this host")
|
.with_about("Set whether this gateway should be enabled for this binding")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -215,29 +234,50 @@ pub async fn list_bindings<Kind: HostApiKind>(
|
|||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct BindingSetPublicParams {
|
pub struct BindingGatewaySetEnabledParams {
|
||||||
internal_port: u16,
|
internal_port: u16,
|
||||||
|
gateway: GatewayId,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
public: Option<bool>,
|
enabled: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_public<Kind: HostApiKind>(
|
pub async fn set_gateway_enabled<Kind: HostApiKind>(
|
||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
BindingSetPublicParams {
|
BindingGatewaySetEnabledParams {
|
||||||
internal_port,
|
internal_port,
|
||||||
public,
|
gateway,
|
||||||
}: BindingSetPublicParams,
|
enabled,
|
||||||
|
}: BindingGatewaySetEnabledParams,
|
||||||
inheritance: Kind::Inheritance,
|
inheritance: Kind::Inheritance,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let enabled = enabled.unwrap_or(true);
|
||||||
|
let gateway_public = ctx
|
||||||
|
.net_controller
|
||||||
|
.net_iface
|
||||||
|
.watcher
|
||||||
|
.ip_info()
|
||||||
|
.get(&gateway)
|
||||||
|
.or_not_found(&gateway)?
|
||||||
|
.public();
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
Kind::host_for(&inheritance, db)?
|
Kind::host_for(&inheritance, db)?
|
||||||
.as_bindings_mut()
|
.as_bindings_mut()
|
||||||
.mutate(|b| {
|
.mutate(|b| {
|
||||||
b.get_mut(&internal_port)
|
let net = &mut b.get_mut(&internal_port).or_not_found(internal_port)?.net;
|
||||||
.or_not_found(internal_port)?
|
if gateway_public {
|
||||||
.net
|
if enabled {
|
||||||
.public = public.unwrap_or(true);
|
net.public_enabled.insert(gateway);
|
||||||
|
} else {
|
||||||
|
net.public_enabled.remove(&gateway);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if enabled {
|
||||||
|
net.private_disabled.remove(&gateway);
|
||||||
|
} else {
|
||||||
|
net.private_disabled.insert(gateway);
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ 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::{from_fn_async, Context, Empty, HandlerExt, OrEmpty, ParentHandler};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use torut::onion::OnionAddressV3;
|
|
||||||
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, DomainConfig, HostAddress};
|
use crate::net::host::address::{address_api, HostAddress, PublicDomainConfig};
|
||||||
use crate::net::host::binding::{binding, BindInfo, BindOptions};
|
use crate::net::host::binding::{binding, BindInfo, BindOptions};
|
||||||
use crate::net::service_interface::HostnameInfo;
|
use crate::net::service_interface::HostnameInfo;
|
||||||
|
use crate::net::tor::OnionAddress;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub mod address;
|
pub mod address;
|
||||||
@@ -29,12 +29,13 @@ pub mod binding;
|
|||||||
pub struct Host {
|
pub struct Host {
|
||||||
pub bindings: BTreeMap<u16, BindInfo>,
|
pub bindings: BTreeMap<u16, BindInfo>,
|
||||||
#[ts(type = "string[]")]
|
#[ts(type = "string[]")]
|
||||||
pub onions: BTreeSet<OnionAddressV3>,
|
pub onions: BTreeSet<OnionAddress>,
|
||||||
#[ts(as = "BTreeMap::<String, DomainConfig>")]
|
pub public_domains: BTreeMap<InternedString, PublicDomainConfig>,
|
||||||
pub domains: BTreeMap<InternedString, DomainConfig>,
|
pub private_domains: BTreeSet<InternedString>,
|
||||||
/// COMPUTED: NetService::update
|
/// COMPUTED: NetService::update
|
||||||
pub hostname_info: BTreeMap<u16, Vec<HostnameInfo>>, // internal port -> Hostnames
|
pub hostname_info: BTreeMap<u16, Vec<HostnameInfo>>, // internal port -> Hostnames
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<Host> for Host {
|
impl AsRef<Host> for Host {
|
||||||
fn as_ref(&self) -> &Host {
|
fn as_ref(&self) -> &Host {
|
||||||
self
|
self
|
||||||
@@ -50,15 +51,23 @@ impl Host {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.map(|address| HostAddress::Onion { address })
|
.map(|address| HostAddress::Onion { address })
|
||||||
.chain(
|
.chain(
|
||||||
self.domains
|
self.public_domains
|
||||||
.iter()
|
.iter()
|
||||||
.map(
|
.map(|(address, config)| HostAddress::Domain {
|
||||||
|(address, DomainConfig { public, acme })| HostAddress::Domain {
|
|
||||||
address: address.clone(),
|
address: address.clone(),
|
||||||
public: *public,
|
public: Some(config.clone()),
|
||||||
acme: acme.clone(),
|
private: self.private_domains.contains(address),
|
||||||
},
|
}),
|
||||||
),
|
)
|
||||||
|
.chain(
|
||||||
|
self.private_domains
|
||||||
|
.iter()
|
||||||
|
.filter(|a| !self.public_domains.contains_key(*a))
|
||||||
|
.map(|address| HostAddress::Domain {
|
||||||
|
address: address.clone(),
|
||||||
|
public: None,
|
||||||
|
private: true,
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,24 +124,22 @@ pub fn host_for<'a>(
|
|||||||
};
|
};
|
||||||
host_info(db, package_id)?.upsert(host_id, || {
|
host_info(db, package_id)?.upsert(host_id, || {
|
||||||
let mut h = Host::new();
|
let mut h = Host::new();
|
||||||
h.onions.insert(
|
h.onions
|
||||||
tor_key
|
.insert(tor_key.or_not_found("generated tor key")?.onion_address());
|
||||||
.or_not_found("generated tor key")?
|
|
||||||
.public()
|
|
||||||
.get_onion_address(),
|
|
||||||
);
|
|
||||||
Ok(h)
|
Ok(h)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_hosts(db: &DatabaseModel) -> impl Iterator<Item = Result<&Model<Host>, Error>> {
|
pub fn all_hosts(db: &mut DatabaseModel) -> impl Iterator<Item = Result<&mut Model<Host>, Error>> {
|
||||||
[Ok(db.as_public().as_server_info().as_network().as_host())]
|
use patch_db::DestructureMut;
|
||||||
|
let destructured = db.as_public_mut().destructure_mut();
|
||||||
|
[Ok(destructured.server_info.as_network_mut().as_host_mut())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(
|
.chain(
|
||||||
[db.as_public().as_package_data().as_entries()]
|
[destructured.package_data.as_entries_mut()]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten_ok()
|
.flatten_ok()
|
||||||
.map(|entry| entry.and_then(|(_, v)| v.as_hosts().as_entries()))
|
.map(|entry| entry.and_then(|(_, v)| v.as_hosts_mut().as_entries_mut()))
|
||||||
.flatten_ok()
|
.flatten_ok()
|
||||||
.map_ok(|(_, v)| v),
|
.map_ok(|(_, v)| v),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,34 +3,43 @@ use rpc_toolkit::{Context, HandlerExt, ParentHandler};
|
|||||||
pub mod acme;
|
pub mod acme;
|
||||||
pub mod dns;
|
pub mod dns;
|
||||||
pub mod forward;
|
pub mod forward;
|
||||||
|
pub mod gateway;
|
||||||
pub mod host;
|
pub mod host;
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
pub mod mdns;
|
pub mod mdns;
|
||||||
pub mod net_controller;
|
pub mod net_controller;
|
||||||
pub mod network_interface;
|
|
||||||
pub mod service_interface;
|
pub mod service_interface;
|
||||||
|
pub mod socks;
|
||||||
pub mod ssl;
|
pub mod ssl;
|
||||||
pub mod static_server;
|
pub mod static_server;
|
||||||
pub mod tor;
|
pub mod tor;
|
||||||
|
pub mod tunnel;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod vhost;
|
pub mod vhost;
|
||||||
pub mod web_server;
|
pub mod web_server;
|
||||||
pub mod wifi;
|
pub mod wifi;
|
||||||
|
|
||||||
pub fn net<C: Context>() -> ParentHandler<C> {
|
pub fn net_api<C: Context>() -> ParentHandler<C> {
|
||||||
ParentHandler::new()
|
ParentHandler::new()
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"tor",
|
"tor",
|
||||||
tor::tor::<C>().with_about("Tor commands such as list-services, logs, and reset"),
|
tor::tor_api::<C>().with_about("Tor commands such as list-services, logs, and reset"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"acme",
|
"acme",
|
||||||
acme::acme::<C>().with_about("Setup automatic clearnet certificate acquisition"),
|
acme::acme_api::<C>().with_about("Setup automatic clearnet certificate acquisition"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"network-interface",
|
"dns",
|
||||||
network_interface::network_interface_api::<C>()
|
dns::dns_api::<C>().with_about("Manage and query DNS"),
|
||||||
.with_about("View and edit network interface configurations"),
|
)
|
||||||
|
.subcommand(
|
||||||
|
"gateway",
|
||||||
|
gateway::gateway_api::<C>().with_about("View and edit gateway configurations"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"tunnel",
|
||||||
|
tunnel::tunnel_api::<C>().with_about("Manage tunnels"),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"vhost",
|
"vhost",
|
||||||
|
|||||||
@@ -9,20 +9,24 @@ use ipnet::IpNet;
|
|||||||
use models::{HostId, OptionExt, PackageId};
|
use models::{HostId, OptionExt, PackageId};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::db::model::public::{NetworkInterfaceInfo, NetworkInterfaceType};
|
||||||
use crate::db::model::Database;
|
use crate::db::model::Database;
|
||||||
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::LanPortForwardController;
|
use crate::net::forward::PortForwardController;
|
||||||
|
use crate::net::gateway::{
|
||||||
|
AndFilter, DynInterfaceFilter, IdFilter, InterfaceFilter, NetworkInterfaceController, OrFilter,
|
||||||
|
PublicFilter, SecureFilter,
|
||||||
|
};
|
||||||
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_for, Host, Hosts};
|
||||||
use crate::net::network_interface::NetworkInterfaceController;
|
use crate::net::service_interface::{GatewayInfo, HostnameInfo, IpHostname, OnionHostname};
|
||||||
use crate::net::service_interface::{HostnameInfo, IpHostname, OnionHostname};
|
use crate::net::socks::SocksController;
|
||||||
use crate::net::tor::TorController;
|
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, TargetInfo, VHostController};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -36,7 +40,8 @@ pub struct NetController {
|
|||||||
pub(super) vhost: VHostController,
|
pub(super) vhost: VHostController,
|
||||||
pub(crate) net_iface: Arc<NetworkInterfaceController>,
|
pub(crate) net_iface: Arc<NetworkInterfaceController>,
|
||||||
pub(super) dns: DnsController,
|
pub(super) dns: DnsController,
|
||||||
pub(super) forward: LanPortForwardController,
|
pub(super) forward: PortForwardController,
|
||||||
|
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>,
|
||||||
}
|
}
|
||||||
@@ -44,18 +49,20 @@ pub struct NetController {
|
|||||||
impl NetController {
|
impl NetController {
|
||||||
pub async fn init(
|
pub async fn init(
|
||||||
db: TypedPatchDb<Database>,
|
db: TypedPatchDb<Database>,
|
||||||
tor_control: SocketAddr,
|
|
||||||
tor_socks: SocketAddr,
|
|
||||||
hostname: &Hostname,
|
hostname: &Hostname,
|
||||||
|
socks_listen: SocketAddr,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let net_iface = Arc::new(NetworkInterfaceController::new(db.clone()));
|
let net_iface = Arc::new(NetworkInterfaceController::new(db.clone()));
|
||||||
|
let tor = TorController::new()?;
|
||||||
|
let socks = SocksController::new(socks_listen, tor.clone())?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
db: db.clone(),
|
db: db.clone(),
|
||||||
tor: TorController::new(tor_control, tor_socks),
|
tor,
|
||||||
vhost: VHostController::new(db, net_iface.clone()),
|
vhost: VHostController::new(db.clone(), net_iface.clone()),
|
||||||
dns: DnsController::init(net_iface.lxcbr_status()).await?,
|
dns: DnsController::init(db, &net_iface.watcher).await?,
|
||||||
forward: LanPortForwardController::new(net_iface.subscribe()),
|
forward: PortForwardController::new(net_iface.watcher.subscribe()),
|
||||||
net_iface,
|
net_iface,
|
||||||
|
socks,
|
||||||
server_hostnames: vec![
|
server_hostnames: vec![
|
||||||
// LAN IP
|
// LAN IP
|
||||||
None,
|
None,
|
||||||
@@ -78,7 +85,7 @@ impl NetController {
|
|||||||
package: PackageId,
|
package: PackageId,
|
||||||
ip: Ipv4Addr,
|
ip: Ipv4Addr,
|
||||||
) -> Result<NetService, Error> {
|
) -> Result<NetService, Error> {
|
||||||
let dns = self.dns.add(Some(package.clone()), ip).await?;
|
let dns = self.dns.add_service(Some(package.clone()), ip)?;
|
||||||
|
|
||||||
let res = NetService::new(NetServiceData {
|
let res = NetService::new(NetServiceData {
|
||||||
id: Some(package),
|
id: Some(package),
|
||||||
@@ -92,7 +99,7 @@ impl NetController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn os_bindings(self: &Arc<Self>) -> Result<NetService, Error> {
|
pub async fn os_bindings(self: &Arc<Self>) -> Result<NetService, Error> {
|
||||||
let dns = self.dns.add(None, HOST_IP.into()).await?;
|
let dns = self.dns.add_service(None, HOST_IP.into())?;
|
||||||
|
|
||||||
let service = NetService::new(NetServiceData {
|
let service = NetService::new(NetServiceData {
|
||||||
id: None,
|
id: None,
|
||||||
@@ -126,9 +133,10 @@ impl NetController {
|
|||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
struct HostBinds {
|
struct HostBinds {
|
||||||
forwards: BTreeMap<u16, (SocketAddr, bool, Arc<()>)>,
|
forwards: BTreeMap<u16, (SocketAddr, DynInterfaceFilter, Arc<()>)>,
|
||||||
vhosts: BTreeMap<(Option<InternedString>, u16), (TargetInfo, Arc<()>)>,
|
vhosts: BTreeMap<(Option<InternedString>, u16), (TargetInfo, Arc<()>)>,
|
||||||
tor: BTreeMap<OnionAddressV3, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>,
|
private_dns: BTreeMap<InternedString, Arc<()>>,
|
||||||
|
tor: BTreeMap<OnionAddress, (OrdMap<u16, SocketAddr>, Vec<Arc<()>>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NetServiceData {
|
pub struct NetServiceData {
|
||||||
@@ -217,9 +225,10 @@ 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, bool)> = BTreeMap::new();
|
let mut forwards: BTreeMap<u16, (SocketAddr, DynInterfaceFilter)> = BTreeMap::new();
|
||||||
let mut vhosts: BTreeMap<(Option<InternedString>, u16), TargetInfo> = BTreeMap::new();
|
let mut vhosts: BTreeMap<(Option<InternedString>, u16), TargetInfo> = BTreeMap::new();
|
||||||
let mut tor: BTreeMap<OnionAddressV3, (TorSecretKeyV3, OrdMap<u16, SocketAddr>)> =
|
let mut private_dns: BTreeSet<InternedString> = BTreeSet::new();
|
||||||
|
let mut tor: BTreeMap<OnionAddress, (TorSecretKey, OrdMap<u16, SocketAddr>)> =
|
||||||
BTreeMap::new();
|
BTreeMap::new();
|
||||||
let mut hostname_info: BTreeMap<u16, Vec<HostnameInfo>> = BTreeMap::new();
|
let mut hostname_info: BTreeMap<u16, Vec<HostnameInfo>> = BTreeMap::new();
|
||||||
let binds = self.binds.entry(id.clone()).or_default();
|
let binds = self.binds.entry(id.clone()).or_default();
|
||||||
@@ -228,7 +237,7 @@ impl NetServiceData {
|
|||||||
|
|
||||||
// LAN
|
// LAN
|
||||||
let server_info = peek.as_public().as_server_info();
|
let server_info = peek.as_public().as_server_info();
|
||||||
let net_ifaces = ctrl.net_iface.ip_info();
|
let net_ifaces = ctrl.net_iface.watcher.ip_info();
|
||||||
let hostname = server_info.as_hostname().de()?;
|
let hostname = server_info.as_hostname().de()?;
|
||||||
for (port, bind) in &host.bindings {
|
for (port, bind) in &host.bindings {
|
||||||
if !bind.enabled {
|
if !bind.enabled {
|
||||||
@@ -255,7 +264,7 @@ impl NetServiceData {
|
|||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(hostname, external),
|
(hostname, external),
|
||||||
TargetInfo {
|
TargetInfo {
|
||||||
public: bind.net.public,
|
filter: bind.net.clone().into_dyn(),
|
||||||
acme: None,
|
acme: None,
|
||||||
addr,
|
addr,
|
||||||
connect_ssl: connect_ssl.clone(),
|
connect_ssl: connect_ssl.clone(),
|
||||||
@@ -270,38 +279,107 @@ impl NetServiceData {
|
|||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(Some(hostname), external),
|
(Some(hostname), external),
|
||||||
TargetInfo {
|
TargetInfo {
|
||||||
public: false,
|
filter: OrFilter(
|
||||||
|
IdFilter(
|
||||||
|
NetworkInterfaceInfo::loopback().0.clone(),
|
||||||
|
),
|
||||||
|
IdFilter(
|
||||||
|
NetworkInterfaceInfo::lxc_bridge().0.clone(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_dyn(),
|
||||||
|
acme: None,
|
||||||
|
addr,
|
||||||
|
connect_ssl: connect_ssl.clone(),
|
||||||
|
},
|
||||||
|
); // TODO: wrap onion ssl stream directly in tor ctrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HostAddress::Domain {
|
||||||
|
address,
|
||||||
|
public,
|
||||||
|
private,
|
||||||
|
} => {
|
||||||
|
if hostnames.insert(address.clone()) {
|
||||||
|
let address = Some(address.clone());
|
||||||
|
if ssl.preferred_external_port == 443 {
|
||||||
|
if let Some(public) = &public {
|
||||||
|
vhosts.insert(
|
||||||
|
(address.clone(), 5443),
|
||||||
|
TargetInfo {
|
||||||
|
filter: AndFilter(
|
||||||
|
bind.net.clone(),
|
||||||
|
AndFilter(
|
||||||
|
IdFilter(public.gateway.clone()),
|
||||||
|
PublicFilter { public: false },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_dyn(),
|
||||||
|
acme: public.acme.clone(),
|
||||||
|
addr,
|
||||||
|
connect_ssl: connect_ssl.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
vhosts.insert(
|
||||||
|
(address.clone(), 443),
|
||||||
|
TargetInfo {
|
||||||
|
filter: AndFilter(
|
||||||
|
bind.net.clone(),
|
||||||
|
if private {
|
||||||
|
OrFilter(
|
||||||
|
IdFilter(public.gateway.clone()),
|
||||||
|
PublicFilter { public: false },
|
||||||
|
)
|
||||||
|
.into_dyn()
|
||||||
|
} else {
|
||||||
|
AndFilter(
|
||||||
|
IdFilter(public.gateway.clone()),
|
||||||
|
PublicFilter { public: true },
|
||||||
|
)
|
||||||
|
.into_dyn()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_dyn(),
|
||||||
|
acme: public.acme.clone(),
|
||||||
|
addr,
|
||||||
|
connect_ssl: connect_ssl.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
vhosts.insert(
|
||||||
|
(address.clone(), 443),
|
||||||
|
TargetInfo {
|
||||||
|
filter: AndFilter(
|
||||||
|
bind.net.clone(),
|
||||||
|
PublicFilter { public: false },
|
||||||
|
)
|
||||||
|
.into_dyn(),
|
||||||
acme: None,
|
acme: None,
|
||||||
addr,
|
addr,
|
||||||
connect_ssl: connect_ssl.clone(),
|
connect_ssl: connect_ssl.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
HostAddress::Domain {
|
if let Some(public) = public {
|
||||||
address,
|
|
||||||
public,
|
|
||||||
acme,
|
|
||||||
} => {
|
|
||||||
if hostnames.insert(address.clone()) {
|
|
||||||
let address = Some(address.clone());
|
|
||||||
if ssl.preferred_external_port == 443 {
|
|
||||||
if public && bind.net.public {
|
|
||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(address.clone(), 5443),
|
(address.clone(), external),
|
||||||
TargetInfo {
|
TargetInfo {
|
||||||
public: false,
|
filter: AndFilter(
|
||||||
acme: acme.clone(),
|
bind.net.clone(),
|
||||||
addr,
|
if private {
|
||||||
connect_ssl: connect_ssl.clone(),
|
OrFilter(
|
||||||
|
IdFilter(public.gateway.clone()),
|
||||||
|
PublicFilter { public: false },
|
||||||
|
)
|
||||||
|
.into_dyn()
|
||||||
|
} else {
|
||||||
|
IdFilter(public.gateway.clone())
|
||||||
|
.into_dyn()
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
}
|
.into_dyn(),
|
||||||
vhosts.insert(
|
acme: public.acme.clone(),
|
||||||
(address.clone(), 443),
|
|
||||||
TargetInfo {
|
|
||||||
public: public && bind.net.public,
|
|
||||||
acme,
|
|
||||||
addr,
|
addr,
|
||||||
connect_ssl: connect_ssl.clone(),
|
connect_ssl: connect_ssl.clone(),
|
||||||
},
|
},
|
||||||
@@ -310,8 +388,12 @@ impl NetServiceData {
|
|||||||
vhosts.insert(
|
vhosts.insert(
|
||||||
(address.clone(), external),
|
(address.clone(), external),
|
||||||
TargetInfo {
|
TargetInfo {
|
||||||
public: public && bind.net.public,
|
filter: AndFilter(
|
||||||
acme,
|
bind.net.clone(),
|
||||||
|
PublicFilter { public: false },
|
||||||
|
)
|
||||||
|
.into_dyn(),
|
||||||
|
acme: None,
|
||||||
addr,
|
addr,
|
||||||
connect_ssl: connect_ssl.clone(),
|
connect_ssl: connect_ssl.clone(),
|
||||||
},
|
},
|
||||||
@@ -322,35 +404,61 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(security) = bind.options.secure {
|
|
||||||
if bind.options.add_ssl.is_some() && security.ssl {
|
|
||||||
// doesn't make sense to have 2 listening ports, both with ssl
|
|
||||||
} else {
|
|
||||||
let external = bind.net.assigned_port.or_not_found("assigned lan port")?;
|
|
||||||
forwards.insert(external, ((self.ip, *port).into(), bind.net.public));
|
|
||||||
}
|
}
|
||||||
|
if bind
|
||||||
|
.options
|
||||||
|
.secure
|
||||||
|
.map_or(true, |s| !(s.ssl && bind.options.add_ssl.is_some()))
|
||||||
|
{
|
||||||
|
let external = bind.net.assigned_port.or_not_found("assigned lan port")?;
|
||||||
|
forwards.insert(
|
||||||
|
external,
|
||||||
|
(
|
||||||
|
(self.ip, *port).into(),
|
||||||
|
AndFilter(
|
||||||
|
SecureFilter {
|
||||||
|
secure: bind.options.secure.is_some(),
|
||||||
|
},
|
||||||
|
bind.net.clone(),
|
||||||
|
)
|
||||||
|
.into_dyn(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let mut bind_hostname_info: Vec<HostnameInfo> =
|
let mut bind_hostname_info: Vec<HostnameInfo> =
|
||||||
hostname_info.remove(port).unwrap_or_default();
|
hostname_info.remove(port).unwrap_or_default();
|
||||||
for (interface, public, ip_info) in
|
for (gateway_id, info) in net_ifaces
|
||||||
net_ifaces.iter().filter_map(|(interface, info)| {
|
.iter()
|
||||||
if let Some(ip_info) = &info.ip_info {
|
.filter(|(id, info)| bind.net.filter(id, info))
|
||||||
Some((interface, info.inbound(), ip_info))
|
{
|
||||||
} else {
|
let gateway = GatewayInfo {
|
||||||
None
|
id: gateway_id.clone(),
|
||||||
}
|
name: info
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.or_else(|| info.ip_info.as_ref().map(|i| i.name.clone()))
|
||||||
|
.unwrap_or_else(|| gateway_id.clone().into()),
|
||||||
|
public: info.public(),
|
||||||
|
};
|
||||||
|
let port = bind.net.assigned_port.filter(|_| {
|
||||||
|
bind.options.secure.map_or(false, |s| {
|
||||||
|
!(s.ssl && bind.options.add_ssl.is_some()) || info.secure()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if !info.public()
|
||||||
|
&& info.ip_info.as_ref().map_or(false, |i| {
|
||||||
|
i.device_type != Some(NetworkInterfaceType::Wireguard)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
if !public {
|
|
||||||
bind_hostname_info.push(HostnameInfo::Ip {
|
bind_hostname_info.push(HostnameInfo::Ip {
|
||||||
network_interface_id: interface.clone(),
|
gateway: gateway.clone(),
|
||||||
public: false,
|
public: false,
|
||||||
hostname: IpHostname::Local {
|
hostname: IpHostname::Local {
|
||||||
value: InternedString::from_display(&{
|
value: InternedString::from_display(&{
|
||||||
let hostname = &hostname;
|
let hostname = &hostname;
|
||||||
lazy_format!("{hostname}.local")
|
lazy_format!("{hostname}.local")
|
||||||
}),
|
}),
|
||||||
port: bind.net.assigned_port,
|
port,
|
||||||
ssl_port: bind.net.assigned_ssl_port,
|
ssl_port: bind.net.assigned_ssl_port,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -358,11 +466,14 @@ impl NetServiceData {
|
|||||||
for address in host.addresses() {
|
for address in host.addresses() {
|
||||||
if let HostAddress::Domain {
|
if let HostAddress::Domain {
|
||||||
address,
|
address,
|
||||||
public: domain_public,
|
public,
|
||||||
..
|
private,
|
||||||
} = address
|
} = address
|
||||||
{
|
{
|
||||||
if !public || (domain_public && bind.net.public) {
|
let private = private && !info.public();
|
||||||
|
let public =
|
||||||
|
public.as_ref().map_or(false, |p| &p.gateway == gateway_id);
|
||||||
|
if public || private {
|
||||||
if bind
|
if bind
|
||||||
.options
|
.options
|
||||||
.add_ssl
|
.add_ssl
|
||||||
@@ -370,23 +481,21 @@ impl NetServiceData {
|
|||||||
.map_or(false, |ssl| ssl.preferred_external_port == 443)
|
.map_or(false, |ssl| ssl.preferred_external_port == 443)
|
||||||
{
|
{
|
||||||
bind_hostname_info.push(HostnameInfo::Ip {
|
bind_hostname_info.push(HostnameInfo::Ip {
|
||||||
network_interface_id: interface.clone(),
|
gateway: gateway.clone(),
|
||||||
public: public && domain_public && bind.net.public, // TODO: check if port forward is active
|
public,
|
||||||
hostname: IpHostname::Domain {
|
hostname: IpHostname::Domain {
|
||||||
domain: address.clone(),
|
value: address.clone(),
|
||||||
subdomain: None,
|
|
||||||
port: None,
|
port: None,
|
||||||
ssl_port: Some(443),
|
ssl_port: Some(443),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
bind_hostname_info.push(HostnameInfo::Ip {
|
bind_hostname_info.push(HostnameInfo::Ip {
|
||||||
network_interface_id: interface.clone(),
|
gateway: gateway.clone(),
|
||||||
public,
|
public,
|
||||||
hostname: IpHostname::Domain {
|
hostname: IpHostname::Domain {
|
||||||
domain: address.clone(),
|
value: address.clone(),
|
||||||
subdomain: None,
|
port,
|
||||||
port: bind.net.assigned_port,
|
|
||||||
ssl_port: bind.net.assigned_ssl_port,
|
ssl_port: bind.net.assigned_ssl_port,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -394,14 +503,15 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !public || bind.net.public {
|
if let Some(ip_info) = &info.ip_info {
|
||||||
if let Some(wan_ip) = ip_info.wan_ip.filter(|_| public) {
|
let public = info.public();
|
||||||
|
if let Some(wan_ip) = ip_info.wan_ip {
|
||||||
bind_hostname_info.push(HostnameInfo::Ip {
|
bind_hostname_info.push(HostnameInfo::Ip {
|
||||||
network_interface_id: interface.clone(),
|
gateway: gateway.clone(),
|
||||||
public,
|
public: true,
|
||||||
hostname: IpHostname::Ipv4 {
|
hostname: IpHostname::Ipv4 {
|
||||||
value: wan_ip,
|
value: wan_ip,
|
||||||
port: bind.net.assigned_port,
|
port,
|
||||||
ssl_port: bind.net.assigned_ssl_port,
|
ssl_port: bind.net.assigned_ssl_port,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -411,11 +521,11 @@ impl NetServiceData {
|
|||||||
IpNet::V4(net) => {
|
IpNet::V4(net) => {
|
||||||
if !public {
|
if !public {
|
||||||
bind_hostname_info.push(HostnameInfo::Ip {
|
bind_hostname_info.push(HostnameInfo::Ip {
|
||||||
network_interface_id: interface.clone(),
|
gateway: gateway.clone(),
|
||||||
public,
|
public,
|
||||||
hostname: IpHostname::Ipv4 {
|
hostname: IpHostname::Ipv4 {
|
||||||
value: net.addr(),
|
value: net.addr(),
|
||||||
port: bind.net.assigned_port,
|
port,
|
||||||
ssl_port: bind.net.assigned_ssl_port,
|
ssl_port: bind.net.assigned_ssl_port,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -423,12 +533,12 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
IpNet::V6(net) => {
|
IpNet::V6(net) => {
|
||||||
bind_hostname_info.push(HostnameInfo::Ip {
|
bind_hostname_info.push(HostnameInfo::Ip {
|
||||||
network_interface_id: interface.clone(),
|
gateway: gateway.clone(),
|
||||||
public: public && !ipv6_is_local(net.addr()),
|
public: public && !ipv6_is_local(net.addr()),
|
||||||
hostname: IpHostname::Ipv6 {
|
hostname: IpHostname::Ipv6 {
|
||||||
value: net.addr(),
|
value: net.addr(),
|
||||||
scope_id: ip_info.scope_id,
|
scope_id: ip_info.scope_id,
|
||||||
port: bind.net.assigned_port,
|
port,
|
||||||
ssl_port: bind.net.assigned_ssl_port,
|
ssl_port: bind.net.assigned_ssl_port,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -438,6 +548,7 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
hostname_info.insert(*port, bind_hostname_info);
|
hostname_info.insert(*port, bind_hostname_info);
|
||||||
|
private_dns.append(&mut hostnames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,7 +598,7 @@ impl NetServiceData {
|
|||||||
.as_key_store()
|
.as_key_store()
|
||||||
.as_onion()
|
.as_onion()
|
||||||
.get_key(tor_addr)?;
|
.get_key(tor_addr)?;
|
||||||
tor.insert(key.public().get_onion_address(), (key, tor_binds.clone()));
|
tor.insert(key.onion_address(), (key, tor_binds.clone()));
|
||||||
for (internal, ports) in &tor_hostname_ports {
|
for (internal, ports) in &tor_hostname_ports {
|
||||||
let mut bind_hostname_info = hostname_info.remove(internal).unwrap_or_default();
|
let mut bind_hostname_info = hostname_info.remove(internal).unwrap_or_default();
|
||||||
bind_hostname_info.push(HostnameInfo::Onion {
|
bind_hostname_info.push(HostnameInfo::Onion {
|
||||||
@@ -509,8 +620,8 @@ impl NetServiceData {
|
|||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeSet<_>>();
|
||||||
for external in all {
|
for external in all {
|
||||||
let mut prev = binds.forwards.remove(&external);
|
let mut prev = binds.forwards.remove(&external);
|
||||||
if let Some((internal, public)) = forwards.remove(&external) {
|
if let Some((internal, filter)) = forwards.remove(&external) {
|
||||||
prev = prev.filter(|(i, p, _)| i == &internal && *p == public);
|
prev = prev.filter(|(i, f, _)| i == &internal && *f == filter);
|
||||||
binds.forwards.insert(
|
binds.forwards.insert(
|
||||||
external,
|
external,
|
||||||
if let Some(prev) = prev {
|
if let Some(prev) = prev {
|
||||||
@@ -518,8 +629,8 @@ impl NetServiceData {
|
|||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
internal,
|
internal,
|
||||||
public,
|
filter.clone(),
|
||||||
ctrl.forward.add(external, public, internal).await?,
|
ctrl.forward.add(external, filter, internal).await?,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -553,6 +664,22 @@ impl NetServiceData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut rm = BTreeSet::new();
|
||||||
|
binds.private_dns.retain(|fqdn, _| {
|
||||||
|
if private_dns.remove(fqdn) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
rm.insert(fqdn.clone());
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for fqdn in private_dns {
|
||||||
|
binds
|
||||||
|
.private_dns
|
||||||
|
.insert(fqdn.clone(), ctrl.dns.add_private_domain(fqdn)?);
|
||||||
|
}
|
||||||
|
ctrl.dns.gc_private_domains(&rm)?;
|
||||||
|
|
||||||
let all = binds
|
let all = binds
|
||||||
.tor
|
.tor
|
||||||
.keys()
|
.keys()
|
||||||
@@ -568,17 +695,15 @@ impl NetServiceData {
|
|||||||
if let Some(prev) = prev {
|
if let Some(prev) = prev {
|
||||||
prev
|
prev
|
||||||
} else {
|
} else {
|
||||||
let rcs = ctrl
|
let service = ctrl.tor.service(key)?;
|
||||||
.tor
|
let rcs = service.proxy_all(tor_binds.iter().map(|(k, v)| (*k, *v)));
|
||||||
.add(key, tor_binds.iter().map(|(k, v)| (*k, *v)).collect())
|
|
||||||
.await?;
|
|
||||||
(tor_binds, rcs)
|
(tor_binds, rcs)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if let Some((_, rc)) = prev {
|
if let Some((_, rc)) = prev {
|
||||||
drop(rc);
|
drop(rc);
|
||||||
ctrl.tor.gc(Some(onion), None).await?;
|
ctrl.tor.gc(Some(onion)).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -662,7 +787,7 @@ impl NetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new(data: NetServiceData) -> Result<Self, Error> {
|
fn new(data: NetServiceData) -> Result<Self, Error> {
|
||||||
let mut ip_info = data.net_controller()?.net_iface.subscribe();
|
let mut ip_info = data.net_controller()?.net_iface.watcher.subscribe();
|
||||||
let data = Arc::new(Mutex::new(data));
|
let data = Arc::new(Mutex::new(data));
|
||||||
let thread_data = data.clone();
|
let thread_data = data.clone();
|
||||||
let sync_task = tokio::spawn(async move {
|
let sync_task = tokio::spawn(async move {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
use lazy_format::lazy_format;
|
use models::{GatewayId, HostId, ServiceInterfaceId};
|
||||||
use models::{HostId, ServiceInterfaceId};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
@@ -13,8 +12,7 @@ use ts_rs::TS;
|
|||||||
#[serde(tag = "kind")]
|
#[serde(tag = "kind")]
|
||||||
pub enum HostnameInfo {
|
pub enum HostnameInfo {
|
||||||
Ip {
|
Ip {
|
||||||
#[ts(type = "string")]
|
gateway: GatewayInfo,
|
||||||
network_interface_id: InternedString,
|
|
||||||
public: bool,
|
public: bool,
|
||||||
hostname: IpHostname,
|
hostname: IpHostname,
|
||||||
},
|
},
|
||||||
@@ -31,6 +29,15 @@ impl HostnameInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct GatewayInfo {
|
||||||
|
pub id: GatewayId,
|
||||||
|
pub name: InternedString,
|
||||||
|
pub public: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -72,9 +79,7 @@ pub enum IpHostname {
|
|||||||
},
|
},
|
||||||
Domain {
|
Domain {
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
domain: InternedString,
|
value: InternedString,
|
||||||
#[ts(type = "string | null")]
|
|
||||||
subdomain: Option<InternedString>,
|
|
||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
ssl_port: Option<u16>,
|
ssl_port: Option<u16>,
|
||||||
},
|
},
|
||||||
@@ -85,15 +90,7 @@ impl IpHostname {
|
|||||||
Self::Ipv4 { value, .. } => InternedString::from_display(value),
|
Self::Ipv4 { value, .. } => InternedString::from_display(value),
|
||||||
Self::Ipv6 { value, .. } => InternedString::from_display(value),
|
Self::Ipv6 { value, .. } => InternedString::from_display(value),
|
||||||
Self::Local { value, .. } => value.clone(),
|
Self::Local { value, .. } => value.clone(),
|
||||||
Self::Domain {
|
Self::Domain { value, .. } => value.clone(),
|
||||||
domain, subdomain, ..
|
|
||||||
} => {
|
|
||||||
if let Some(subdomain) = subdomain {
|
|
||||||
InternedString::from_display(&lazy_format!("{subdomain}.{domain}"))
|
|
||||||
} else {
|
|
||||||
domain.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
176
core/startos/src/net/socks.rs
Normal file
176
core/startos/src/net/socks.rs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use helpers::NonDetachingJoinHandle;
|
||||||
|
use socks5_impl::protocol::{Address, Reply};
|
||||||
|
use socks5_impl::server::auth::NoAuth;
|
||||||
|
use socks5_impl::server::{AuthAdaptor, ClientConnection, Server};
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
|
use crate::net::tor::TorController;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
|
use crate::HOST_IP;
|
||||||
|
|
||||||
|
pub const DEFAULT_SOCKS_LISTEN: SocketAddr = SocketAddr::V4(SocketAddrV4::new(
|
||||||
|
Ipv4Addr::new(HOST_IP[0], HOST_IP[1], HOST_IP[2], HOST_IP[3]),
|
||||||
|
9050,
|
||||||
|
));
|
||||||
|
|
||||||
|
pub struct SocksController {
|
||||||
|
_thread: NonDetachingJoinHandle<()>,
|
||||||
|
}
|
||||||
|
impl SocksController {
|
||||||
|
pub fn new(listen: SocketAddr, tor: TorController) -> Result<Self, Error> {
|
||||||
|
Ok(Self {
|
||||||
|
_thread: tokio::spawn(async move {
|
||||||
|
let auth: AuthAdaptor<()> = Arc::new(NoAuth);
|
||||||
|
let listener;
|
||||||
|
loop {
|
||||||
|
if let Some(l) = TcpListener::bind(listen)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)
|
||||||
|
.log_err()
|
||||||
|
{
|
||||||
|
listener = l;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
let (bg, mut runner) = BackgroundJobQueue::new();
|
||||||
|
runner
|
||||||
|
.run_while(async {
|
||||||
|
let server = Server::new(listener, auth);
|
||||||
|
loop {
|
||||||
|
match server.accept().await {
|
||||||
|
Ok((stream, _)) => {
|
||||||
|
let tor = tor.clone();
|
||||||
|
bg.add_job(async move {
|
||||||
|
if let Err(e) = async {
|
||||||
|
match stream
|
||||||
|
.authenticate()
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?
|
||||||
|
.0
|
||||||
|
.wait_request()
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?
|
||||||
|
{
|
||||||
|
ClientConnection::Connect(
|
||||||
|
reply,
|
||||||
|
Address::DomainAddress(domain, port),
|
||||||
|
) if domain.ends_with(".onion") => {
|
||||||
|
if let Ok(mut target) = tor
|
||||||
|
.connect_onion(&domain.parse()?, port)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let mut sock = reply
|
||||||
|
.reply(
|
||||||
|
Reply::Succeeded,
|
||||||
|
Address::unspecified(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
tokio::io::copy_bidirectional(
|
||||||
|
&mut sock,
|
||||||
|
&mut target,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
} else {
|
||||||
|
let mut sock = reply
|
||||||
|
.reply(
|
||||||
|
Reply::HostUnreachable,
|
||||||
|
Address::unspecified(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
sock.shutdown()
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClientConnection::Connect(reply, addr) => {
|
||||||
|
if let Ok(mut target) = match addr {
|
||||||
|
Address::DomainAddress(domain, port) => {
|
||||||
|
TcpStream::connect((domain, port)).await
|
||||||
|
}
|
||||||
|
Address::SocketAddress(addr) => {
|
||||||
|
TcpStream::connect(addr).await
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
let mut sock = reply
|
||||||
|
.reply(
|
||||||
|
Reply::Succeeded,
|
||||||
|
Address::unspecified(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
tokio::io::copy_bidirectional(
|
||||||
|
&mut sock,
|
||||||
|
&mut target,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
} else {
|
||||||
|
let mut sock = reply
|
||||||
|
.reply(
|
||||||
|
Reply::HostUnreachable,
|
||||||
|
Address::unspecified(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
sock.shutdown()
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClientConnection::Bind(bind, _) => {
|
||||||
|
let mut sock = bind
|
||||||
|
.reply(
|
||||||
|
Reply::CommandNotSupported,
|
||||||
|
Address::unspecified(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
sock.shutdown()
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
}
|
||||||
|
ClientConnection::UdpAssociate(associate, _) => {
|
||||||
|
let mut sock = associate
|
||||||
|
.reply(
|
||||||
|
Reply::CommandNotSupported,
|
||||||
|
Address::unspecified(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
sock.shutdown()
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Network)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<_, Error>(())
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::trace!("SOCKS5 Stream Error: {e}");
|
||||||
|
tracing::trace!("{e:?}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("SOCKS5 TCP Accept Error: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::cmp::{min, Ordering};
|
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;
|
||||||
@@ -13,18 +13,18 @@ use openssl::ec::{EcGroup, EcKey};
|
|||||||
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::{X509Builder, X509Extension, X509NameBuilder, X509};
|
use openssl::x509::{X509, X509Builder, X509Extension, X509NameBuilder};
|
||||||
use openssl::*;
|
use openssl::*;
|
||||||
use patch_db::HasModel;
|
use patch_db::HasModel;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use crate::SOURCE_DATE;
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::hostname::Hostname;
|
use crate::hostname::Hostname;
|
||||||
use crate::init::check_time_is_synchronized;
|
use crate::init::check_time_is_synchronized;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
use crate::SOURCE_DATE;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
#[model = "Model<Self>"]
|
#[model = "Model<Self>"]
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ use std::sync::Arc;
|
|||||||
use std::time::UNIX_EPOCH;
|
use std::time::UNIX_EPOCH;
|
||||||
|
|
||||||
use async_compression::tokio::bufread::GzipEncoder;
|
use async_compression::tokio::bufread::GzipEncoder;
|
||||||
|
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::{Redirect, Response};
|
||||||
use axum::routing::{any, get};
|
use axum::routing::{any, get};
|
||||||
use axum::Router;
|
|
||||||
use base64::display::Base64Display;
|
use base64::display::Base64Display;
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
use futures::future::ready;
|
use futures::future::ready;
|
||||||
@@ -37,12 +37,12 @@ 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::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
|
||||||
use crate::rpc_continuations::{Guid, RpcContinuations};
|
use crate::rpc_continuations::{Guid, RpcContinuations};
|
||||||
|
use crate::s9pk::S9pk;
|
||||||
|
use crate::s9pk::merkle_archive::source::FileSource;
|
||||||
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
use crate::s9pk::merkle_archive::source::http::HttpSource;
|
||||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile;
|
||||||
use crate::s9pk::merkle_archive::source::FileSource;
|
use crate::sign::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||||
use crate::s9pk::S9pk;
|
|
||||||
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;
|
||||||
@@ -55,10 +55,10 @@ 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 = "daemon", not(feature = "test")))]
|
#[cfg(all(feature = "startd", not(feature = "test")))]
|
||||||
const EMBEDDED_UIS: Dir<'_> =
|
const EMBEDDED_UIS: Dir<'_> =
|
||||||
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static");
|
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../web/dist/static");
|
||||||
#[cfg(not(all(feature = "daemon", not(feature = "test"))))]
|
#[cfg(not(all(feature = "startd", not(feature = "test"))))]
|
||||||
const EMBEDDED_UIS: Dir<'_> = Dir::new("", &[]);
|
const EMBEDDED_UIS: Dir<'_> = Dir::new("", &[]);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user