merge from master and fix typescript errors

This commit is contained in:
Matt Hill
2023-11-08 15:44:05 -07:00
133 changed files with 3006 additions and 19797 deletions

View File

@@ -12,9 +12,9 @@ on:
- dev - dev
- unstable - unstable
- dev-unstable - dev-unstable
- podman - docker
- dev-podman - dev-docker
- dev-unstable-podman - dev-unstable-docker
runner: runner:
type: choice type: choice
description: Runner description: Runner
@@ -31,6 +31,13 @@ on:
- aarch64 - aarch64
- aarch64-nonfree - aarch64-nonfree
- raspberrypi - raspberrypi
deploy:
type: choice
description: Deploy
options:
- NONE
- alpha
- beta
push: push:
branches: branches:
- master - master
@@ -42,11 +49,55 @@ on:
env: env:
NODEJS_VERSION: "18.15.0" NODEJS_VERSION: "18.15.0"
ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev-podman''))[github.event.inputs.environment == ''NONE''] }}' ENVIRONMENT: '${{ fromJson(format(''["{0}", ""]'', github.event.inputs.environment || ''dev''))[github.event.inputs.environment == ''NONE''] }}'
jobs: jobs:
all: compile:
name: Build name: Compile Base Binaries
strategy:
fail-fast: true
matrix:
arch: >-
${{
fromJson('{
"x86_64": ["x86_64"],
"x86_64-nonfree": ["x86_64"],
"aarch64": ["aarch64"],
"aarch64-nonfree": ["aarch64"],
"raspberrypi": ["aarch64"],
"ALL": ["x86_64", "aarch64"]
}')[github.event.inputs.platform || 'ALL']
}}
runs-on: ${{ fromJson('["ubuntu-22.04", "buildjet-32vcpu-ubuntu-2204"]')[github.event.inputs.runner == 'fast'] }}
steps:
- run: |
sudo mount -t tmpfs tmpfs .
if: ${{ github.event.inputs.runner == 'fast' }}
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODEJS_VERSION }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Make
run: make ARCH=${{ matrix.arch }} compiled-${{ matrix.arch }}.tar
- uses: actions/upload-artifact@v3
with:
name: compiled-${{ matrix.arch }}.tar
path: compiled-${{ matrix.arch }}.tar
image:
name: Build Image
needs: [compile]
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -68,86 +119,30 @@ jobs:
format( format(
'["ubuntu-22.04", "{0}"]', '["ubuntu-22.04", "{0}"]',
fromJson('{ fromJson('{
"x86_64": ["buildjet-32vcpu-ubuntu-2204", "buildjet-32vcpu-ubuntu-2204"], "x86_64": "buildjet-8vcpu-ubuntu-2204",
"x86_64-nonfree": ["buildjet-32vcpu-ubuntu-2204", "buildjet-32vcpu-ubuntu-2204"], "x86_64-nonfree": "buildjet-8vcpu-ubuntu-2204",
"aarch64": ["buildjet-16vcpu-ubuntu-2204-arm", "buildjet-32vcpu-ubuntu-2204-arm"], "aarch64": "buildjet-8vcpu-ubuntu-2204-arm",
"aarch64-nonfree": ["buildjet-16vcpu-ubuntu-2204-arm", "buildjet-32vcpu-ubuntu-2204-arm"], "aarch64-nonfree": "buildjet-8vcpu-ubuntu-2204-arm",
"raspberrypi": ["buildjet-16vcpu-ubuntu-2204-arm", "buildjet-32vcpu-ubuntu-2204-arm"], "raspberrypi": "buildjet-8vcpu-ubuntu-2204-arm",
}')[matrix.platform][github.event.inputs.platform == matrix.platform] }')[matrix.platform]
) )
)[github.event.inputs.runner == 'fast'] )[github.event.inputs.runner == 'fast']
}} }}
env:
ARCH: >-
${{
fromJson('{
"x86_64": "x86_64",
"x86_64-nonfree": "x86_64",
"aarch64": "aarch64",
"aarch64-nonfree": "aarch64",
"raspberrypi": "aarch64",
}')[matrix.platform]
}}
steps: steps:
- name: Free space
run: df -h && rm -rf /opt/hostedtoolcache* && df -h
if: ${{ github.event.inputs.runner != 'fast' }}
- run: |
sudo mount -t tmpfs tmpfs .
if: ${{ github.event.inputs.runner == 'fast' && (matrix.platform == 'x86_64' || matrix.platform == 'x86_64-nonfree' || github.event.inputs.platform == matrix.platform) }}
- uses: actions/checkout@v3
with:
repository: Start9Labs/embassy-os-deb
path: embassy-os-deb
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
path: embassy-os-deb/embassyos-0.3.x
- run: |
cp -r debian embassyos-0.3.x/
VERSION=0.3.x ./control.sh
cp embassyos-0.3.x/backend/startd.service embassyos-0.3.x/debian/embassyos.startd.service
working-directory: embassy-os-deb
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODEJS_VERSION }}
- uses: actions/cache@v3
with:
path: /var/lib/docker
key: ${{ runner.os }}-${{ matrix.platform }}-docker-cache
- name: Get npm cache directory
id: npm-cache-dir
run: |
echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
id: npm-cache
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install \
debmake \
debhelper-compat \
crossbuild-essential-arm64
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Run dpkg build
working-directory: embassy-os-deb
run: "make VERSION=0.3.x TAG=${{ github.ref_name }}"
env:
OS_ARCH: ${{ matrix.platform }}
- uses: actions/checkout@v3
with:
repository: Start9Labs/startos-image-recipes
path: startos-image-recipes
ref: feature/podman
- name: Install dependencies - name: Install dependencies
run: | run: |
@@ -166,52 +161,77 @@ jobs:
- run: sudo mount -t tmpfs tmpfs /var/tmp/debspawn - run: sudo mount -t tmpfs tmpfs /var/tmp/debspawn
if: ${{ github.event.inputs.runner == 'fast' && (matrix.platform == 'x86_64' || matrix.platform == 'x86_64-nonfree') }} if: ${{ github.event.inputs.runner == 'fast' && (matrix.platform == 'x86_64' || matrix.platform == 'x86_64-nonfree') }}
- uses: actions/cache@v3 - name: Download compiled artifacts
uses: actions/download-artifact@v3
with: with:
path: /var/lib/debspawn name: compiled-${{ env.ARCH }}.tar
key: ${{ runner.os }}-${{ matrix.platform }}-debspawn-init
- run: "mkdir -p startos-image-recipes/overlays/deb" - name: Extract compiled artifacts
run: tar -xvf compiled-${{ env.ARCH }}.tar
- run: "mv embassy-os-deb/embassyos_0.3.x-1_*.deb startos-image-recipes/overlays/deb/" - name: Prevent rebuild of compiled artifacts
run: |
- run: "sudo rm -rf embassy-os-deb ${{ steps.npm-cache-dir.outputs.dir }} $HOME/.cargo" mkdir -p frontend/dist/raw
PLATFORM=${{ matrix.platform }} make -t compiled-${{ env.ARCH }}.tar
- name: Run iso build - name: Run iso build
working-directory: startos-image-recipes run: PLATFORM=${{ matrix.platform }} make iso
run: | if: ${{ matrix.platform != 'raspberrypi' }}
./run-local-build.sh ${{ matrix.platform }}
- name: Run img build
run: PLATFORM=${{ matrix.platform }} make img
if: ${{ matrix.platform == 'raspberrypi' }}
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
name: ${{ matrix.platform }}.squashfs name: ${{ matrix.platform }}.squashfs
path: startos-image-recipes/results/*.squashfs path: results/*.squashfs
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
name: ${{ matrix.platform }}.iso name: ${{ matrix.platform }}.iso
path: startos-image-recipes/results/*.iso path: results/*.iso
if: ${{ matrix.platform != 'raspberrypi' }} if: ${{ matrix.platform != 'raspberrypi' }}
- uses: actions/checkout@v3
with:
submodules: recursive
path: start-os
if: ${{ matrix.platform == 'raspberrypi' }}
- run: "mv startos-image-recipes/results/startos-*_raspberrypi.squashfs start-os/startos.raspberrypi.squashfs"
if: ${{ matrix.platform == 'raspberrypi' }}
- run: rm -rf startos-image-recipes
if: ${{ matrix.platform == 'raspberrypi' }}
- name: Build image
working-directory: start-os
run: make startos_raspberrypi.img
if: ${{ matrix.platform == 'raspberrypi' }}
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
name: raspberrypi.img name: ${{ matrix.platform }}.img
path: start-os/startos-*_raspberrypi.img path: results/*.img
if: ${{ matrix.platform == 'raspberrypi' }} if: ${{ matrix.platform == 'raspberrypi' }}
- name: Upload OTA to registry
run: >-
PLATFORM=${{ matrix.platform }} make upload-ota TARGET="${{
fromJson('{
"alpha": "alpha-registry-x.start9.com",
"beta": "beta-registry.start9.com",
}')[github.event.inputs.deploy]
}}" KEY="${{
fromJson(
format('{{
"alpha": "{0}",
"beta": "{1}",
}}', secrets.ALPHA_INDEX_KEY, secrets.BETA_INDEX_KEY)
)[github.event.inputs.deploy]
}}"
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
index:
if: ${{ github.event.inputs.deploy != '' && github.event.inputs.deploy != 'NONE' }}
needs: [image]
runs-on: ubuntu-22.04
steps:
- run: >-
curl "https://${{
fromJson('{
"alpha": "alpha-registry-x.start9.com",
"beta": "beta-registry.start9.com",
}')[github.event.inputs.deploy]
}}:8443/resync.cgi?key=${{
fromJson(
format('{{
"alpha": "{0}",
"beta": "{1}",
}}', secrets.ALPHA_INDEX_KEY, secrets.BETA_INDEX_KEY)
)[github.event.inputs.deploy]
}}"

8
.gitignore vendored
View File

@@ -16,13 +16,15 @@ deploy_web.sh
secrets.db secrets.db
.vscode/ .vscode/
/cargo-deps/**/* /cargo-deps/**/*
/PLATFORM.txt
/ENVIRONMENT.txt /ENVIRONMENT.txt
/GIT_HASH.txt /GIT_HASH.txt
/VERSION.txt /VERSION.txt
/embassyos-*.tar.gz
/eos-*.tar.gz /eos-*.tar.gz
/*.deb /*.deb
/target /target
/*.squashfs /*.squashfs
/debian /results
/DEBIAN /dpkg-workdir
/compiled.tar
/compiled-*.tar

5607
Cargo.lock generated

File diff suppressed because it is too large Load Diff

163
Makefile
View File

@@ -1,25 +1,31 @@
OS_ARCH := $(shell echo "${OS_ARCH}") PLATFORM_FILE := $(shell ./check-platform.sh)
ARCH := $(shell if [ "$(OS_ARCH)" = "raspberrypi" ]; then echo aarch64; else echo $(OS_ARCH) | sed 's/-nonfree$$//g'; fi) ENVIRONMENT_FILE := $(shell ./check-environment.sh)
ENVIRONMENT_FILE = $(shell ./check-environment.sh) GIT_HASH_FILE := $(shell ./check-git-hash.sh)
GIT_HASH_FILE = $(shell ./check-git-hash.sh) VERSION_FILE := $(shell ./check-version.sh)
VERSION_FILE = $(shell ./check-version.sh) BASENAME := $(shell ./basename.sh)
PLATFORM := $(shell if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)
ARCH := $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo aarch64; else echo $(PLATFORM) | sed 's/-nonfree$$//g'; fi)
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
EMBASSY_BINS := backend/target/$(ARCH)-unknown-linux-gnu/release/startbox libs/target/aarch64-unknown-linux-musl/release/embassy_container_init libs/target/x86_64-unknown-linux-musl/release/embassy_container_init EMBASSY_BINS := backend/target/$(ARCH)-unknown-linux-gnu/release/startbox libs/target/aarch64-unknown-linux-musl/release/embassy_container_init libs/target/x86_64-unknown-linux-musl/release/embassy_container_init
EMBASSY_UIS := frontend/dist/raw/ui frontend/dist/raw/setup-wizard frontend/dist/raw/diagnostic-ui frontend/dist/raw/install-wizard EMBASSY_UIS := frontend/dist/raw/ui frontend/dist/raw/setup-wizard frontend/dist/raw/diagnostic-ui frontend/dist/raw/install-wizard
BUILD_SRC := $(shell find build) BUILD_SRC := $(shell git ls-files build) build/lib/depends build/lib/conflicts
DEBIAN_SRC := $(shell git ls-files debian/)
IMAGE_RECIPE_SRC := $(shell git ls-files image-recipe/)
EMBASSY_SRC := backend/startd.service $(BUILD_SRC) EMBASSY_SRC := backend/startd.service $(BUILD_SRC)
COMPAT_SRC := $(shell find system-images/compat/ -not -path 'system-images/compat/target/*' -and -not -name *.tar -and -not -name target) COMPAT_SRC := $(shell git ls-files system-images/compat/)
UTILS_SRC := $(shell find system-images/utils/ -not -name *.tar) UTILS_SRC := $(shell git ls-files system-images/utils/)
BINFMT_SRC := $(shell find system-images/binfmt/ -not -name *.tar) BINFMT_SRC := $(shell git ls-files system-images/binfmt/)
BACKEND_SRC := $(shell find backend/src) $(shell find backend/migrations) $(shell find patch-db/*/src) $(shell find libs/*/src) libs/*/Cargo.toml backend/Cargo.toml backend/Cargo.lock frontend/dist/static BACKEND_SRC := $(shell git ls-files backend) $(shell git ls-files --recurse-submodules patch-db) $(shell git ls-files libs) frontend/dist/static
FRONTEND_SHARED_SRC := $(shell find frontend/projects/shared) $(shell ls -p frontend/ | grep -v / | sed 's/^/frontend\//g') frontend/package.json frontend/node_modules frontend/config.json patch-db/client/dist frontend/patchdb-ui-seed.json FRONTEND_SHARED_SRC := $(shell git ls-files frontend/projects/shared) $(shell ls -p frontend/ | grep -v / | sed 's/^/frontend\//g') frontend/node_modules frontend/config.json patch-db/client/dist frontend/patchdb-ui-seed.json
FRONTEND_UI_SRC := $(shell find frontend/projects/ui) FRONTEND_UI_SRC := $(shell git ls-files frontend/projects/ui)
FRONTEND_SETUP_WIZARD_SRC := $(shell find frontend/projects/setup-wizard) FRONTEND_SETUP_WIZARD_SRC := $(shell git ls-files frontend/projects/setup-wizard)
FRONTEND_DIAGNOSTIC_UI_SRC := $(shell find frontend/projects/diagnostic-ui) FRONTEND_DIAGNOSTIC_UI_SRC := $(shell git ls-files frontend/projects/diagnostic-ui)
FRONTEND_INSTALL_WIZARD_SRC := $(shell find frontend/projects/install-wizard) FRONTEND_INSTALL_WIZARD_SRC := $(shell git ls-files frontend/projects/install-wizard)
PATCH_DB_CLIENT_SRC := $(shell find patch-db/client -not -path patch-db/client/dist -and -not -path patch-db/client/node_modules) 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)
ALL_TARGETS := $(EMBASSY_BINS) system-images/compat/docker-images/$(ARCH).tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar $(EMBASSY_SRC) $(shell if [ "$(OS_ARCH)" = "raspberrypi" ]; then echo cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep; fi) $(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then echo cargo-deps/$(ARCH)-unknown-linux-gnu/release/tokio-console; fi') $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) COMPILED_TARGETS := $(EMBASSY_BINS) system-images/compat/docker-images/$(ARCH).tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar
ALL_TARGETS := $(EMBASSY_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep; fi) $(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then echo cargo-deps/$(ARCH)-unknown-linux-gnu/release/tokio-console; fi') $(PLATFORM_FILE)
ifeq ($(REMOTE),) ifeq ($(REMOTE),)
mkdir = mkdir -p $1 mkdir = mkdir -p $1
@@ -40,14 +46,14 @@ define cp
endef endef
endif endif
.DELETE_ON_ERROR: .DELETE_ON_ERROR:
.PHONY: all gzip install clean format sdk snapshots frontends ui backend reflash startos_raspberrypi.img sudo wormhole .PHONY: all metadata install clean format sdk snapshots frontends ui backend reflash deb $(IMAGE_TYPE) squashfs sudo wormhole
all: $(ALL_TARGETS) all: $(ALL_TARGETS)
metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
sudo: sudo:
sudo true sudo true
@@ -64,9 +70,13 @@ clean:
rm -rf patch-db/client/dist rm -rf patch-db/client/dist
rm -rf patch-db/target rm -rf patch-db/target
rm -rf cargo-deps rm -rf cargo-deps
rm ENVIRONMENT.txt rm -rf dpkg-workdir
rm GIT_HASH.txt rm -rf image-recipe/deb
rm VERSION.txt rm -rf results
rm -f ENVIRONMENT.txt
rm -f PLATFORM.txt
rm -f GIT_HASH.txt
rm -f VERSION.txt
format: format:
cd backend && cargo +nightly fmt cd backend && cargo +nightly fmt
@@ -75,8 +85,20 @@ format:
sdk: sdk:
cd backend/ && ./install-sdk.sh cd backend/ && ./install-sdk.sh
startos_raspberrypi.img: $(BUILD_SRC) startos.raspberrypi.squashfs $(VERSION_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) | sudo deb: results/$(BASENAME).deb
./build/raspberrypi/make-image.sh
debian/control: build/lib/depends build/lib/conflicts
./debuild/control.sh
results/$(BASENAME).deb: dpkg-build.sh $(DEBIAN_SRC) $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
PLATFORM=$(PLATFORM) ./dpkg-build.sh
$(IMAGE_TYPE): results/$(BASENAME).$(IMAGE_TYPE)
squashfs: results/$(BASENAME).squashfs
results/$(BASENAME).$(IMAGE_TYPE) results/$(BASENAME).squashfs: $(IMAGE_RECIPE_SRC) results/$(BASENAME).deb
./image-recipe/run-local-build.sh "results/$(BASENAME).deb"
# For creating os images. DO NOT USE # For creating os images. DO NOT USE
install: $(ALL_TARGETS) install: $(ALL_TARGETS)
@@ -88,58 +110,68 @@ install: $(ALL_TARGETS)
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-deno) $(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/start-deno)
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/avahi-alias) $(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/avahi-alias)
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/embassy-cli) $(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/embassy-cli)
if [ "$(OS_ARCH)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi if [ "$(PLATFORM)" = "raspberrypi" ]; then $(call cp,cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep,$(DESTDIR)/usr/bin/pi-beep); fi
if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then $(call cp,cargo-deps/$(ARCH)-unknown-linux-gnu/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); fi if /bin/bash -c '[[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]'; then $(call cp,cargo-deps/$(ARCH)-unknown-linux-gnu/release/tokio-console,$(DESTDIR)/usr/bin/tokio-console); fi
$(call mkdir,$(DESTDIR)/lib/systemd/system)
$(call cp,backend/startd.service,$(DESTDIR)/lib/systemd/system/startd.service)
$(call mkdir,$(DESTDIR)/usr/lib) $(call mkdir,$(DESTDIR)/usr/lib)
$(call rm,$(DESTDIR)/usr/lib/embassy) $(call rm,$(DESTDIR)/usr/lib/startos)
$(call cp,build/lib,$(DESTDIR)/usr/lib/embassy) $(call cp,build/lib,$(DESTDIR)/usr/lib/startos)
$(call cp,ENVIRONMENT.txt,$(DESTDIR)/usr/lib/embassy/ENVIRONMENT.txt) $(call cp,PLATFORM.txt,$(DESTDIR)/usr/lib/startos/PLATFORM.txt)
$(call cp,GIT_HASH.txt,$(DESTDIR)/usr/lib/embassy/GIT_HASH.txt) $(call cp,ENVIRONMENT.txt,$(DESTDIR)/usr/lib/startos/ENVIRONMENT.txt)
$(call cp,VERSION.txt,$(DESTDIR)/usr/lib/embassy/VERSION.txt) $(call cp,GIT_HASH.txt,$(DESTDIR)/usr/lib/startos/GIT_HASH.txt)
$(call cp,VERSION.txt,$(DESTDIR)/usr/lib/startos/VERSION.txt)
$(call mkdir,$(DESTDIR)/usr/lib/embassy/container) $(call mkdir,$(DESTDIR)/usr/lib/startos/container)
$(call cp,libs/target/aarch64-unknown-linux-musl/release/embassy_container_init,$(DESTDIR)/usr/lib/embassy/container/embassy_container_init.arm64) $(call cp,libs/target/aarch64-unknown-linux-musl/release/embassy_container_init,$(DESTDIR)/usr/lib/startos/container/embassy_container_init.arm64)
$(call cp,libs/target/x86_64-unknown-linux-musl/release/embassy_container_init,$(DESTDIR)/usr/lib/embassy/container/embassy_container_init.amd64) $(call cp,libs/target/x86_64-unknown-linux-musl/release/embassy_container_init,$(DESTDIR)/usr/lib/startos/container/embassy_container_init.amd64)
$(call mkdir,$(DESTDIR)/usr/lib/embassy/system-images) $(call mkdir,$(DESTDIR)/usr/lib/startos/system-images)
$(call cp,system-images/compat/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/embassy/system-images/compat.tar) $(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/embassy/system-images/utils.tar) $(call cp,system-images/utils/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/utils.tar)
$(call cp,system-images/binfmt/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/embassy/system-images/binfmt.tar) $(call cp,system-images/binfmt/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/binfmt.tar)
update-overlay: update-overlay: $(ALL_TARGETS)
@echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m" @echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m"
@echo "\033[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m" @echo "\033[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m"
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
@if [ "`ssh $(REMOTE) 'cat /usr/lib/embassy/VERSION.txt'`" != "`cat ./VERSION.txt`" ]; then >&2 echo "StartOS requires migrations: update-overlay is unavailable." && false; fi @if [ "`ssh $(REMOTE) 'cat /usr/lib/startos/VERSION.txt'`" != "`cat ./VERSION.txt`" ]; then >&2 echo "StartOS requires migrations: update-overlay is unavailable." && false; fi
$(call ssh,"sudo systemctl stop startd") $(call ssh,"sudo systemctl stop startd")
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) OS_ARCH=$(OS_ARCH) $(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) PLATFORM=$(PLATFORM)
$(call ssh,"sudo systemctl start startd") $(call ssh,"sudo systemctl start startd")
wormhole: backend/target/$(ARCH)-unknown-linux-gnu/release/startbox wormhole: backend/target/$(ARCH)-unknown-linux-gnu/release/startbox
@wormhole send backend/target/$(ARCH)-unknown-linux-gnu/release/startbox 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo /usr/lib/embassy/scripts/chroot-and-upgrade \"cd /usr/bin && rm startbox && wormhole receive --accept-file %s && chmod +x startbox\"\n", $$3 }' @wormhole send backend/target/$(ARCH)-unknown-linux-gnu/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 }'
update: update: $(ALL_TARGETS)
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
$(call ssh,"sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/") $(call ssh,"sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/")
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/embassy/next OS_ARCH=$(OS_ARCH) $(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/embassy/next PLATFORM=$(PLATFORM)
$(call ssh,'sudo NO_SYNC=1 /media/embassy/next/usr/lib/embassy/scripts/chroot-and-upgrade "apt-get install -y $(shell cat ./build/lib/depends)"') $(call ssh,'sudo NO_SYNC=1 /media/embassy/next/usr/lib/startos/scripts/chroot-and-upgrade "apt-get install -y $(shell cat ./build/lib/depends)"')
emulate-reflash: emulate-reflash: $(ALL_TARGETS)
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi @if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
$(call ssh,"sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/") $(call ssh,"sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/")
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/embassy/next OS_ARCH=$(OS_ARCH) $(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/embassy/next PLATFORM=$(PLATFORM)
$(call ssh,"sudo touch /media/embassy/config/upgrade && sudo rm -f /media/embassy/config/disk.guid && sudo sync && sudo reboot") $(call ssh,"sudo touch /media/embassy/config/upgrade && sudo rm -f /media/embassy/config/disk.guid && sudo sync && sudo reboot")
system-images/compat/docker-images/aarch64.tar system-images/compat/docker-images/x86_64.tar: $(COMPAT_SRC) backend/Cargo.lock | sudo upload-ota: results/$(BASENAME).squashfs
cd system-images/compat && make && touch docker-images/*.tar TARGET=$(TARGET) KEY=$(KEY) ./upload-ota.sh
system-images/utils/docker-images/aarch64.tar system-images/utils/docker-images/x86_64.tar: $(UTILS_SRC) | sudo build/lib/depends build/lib/conflicts: build/dpkg-deps/*
cd system-images/utils && make && touch docker-images/*.tar build/dpkg-deps/generate.sh
system-images/binfmt/docker-images/aarch64.tar system-images/binfmt/docker-images/x86_64.tar: $(BINFMT_SRC) | sudo system-images/compat/docker-images/$(ARCH).tar: $(COMPAT_SRC) backend/Cargo.lock
cd system-images/binfmt && make && touch docker-images/*.tar cd system-images/compat && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
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
snapshots: libs/snapshot_creator/Cargo.toml snapshots: libs/snapshot_creator/Cargo.toml
cd libs/ && ./build-v8-snapshot.sh cd libs/ && ./build-v8-snapshot.sh
@@ -152,30 +184,26 @@ $(EMBASSY_BINS): $(BACKEND_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) frontend/pa
frontend/node_modules: frontend/package.json frontend/node_modules: frontend/package.json
npm --prefix frontend ci npm --prefix frontend ci
frontend/dist/raw/ui: $(FRONTEND_UI_SRC) $(FRONTEND_SHARED_SRC) $(ENVIRONMENT_FILE) frontend/dist/raw/ui: $(FRONTEND_UI_SRC) $(FRONTEND_SHARED_SRC)
npm --prefix frontend run build:ui npm --prefix frontend run build:ui
frontend/dist/raw/setup-wizard: $(FRONTEND_SETUP_WIZARD_SRC) $(FRONTEND_SHARED_SRC) $(ENVIRONMENT_FILE) frontend/dist/raw/setup-wizard: $(FRONTEND_SETUP_WIZARD_SRC) $(FRONTEND_SHARED_SRC)
npm --prefix frontend run build:setup npm --prefix frontend run build:setup
frontend/dist/raw/diagnostic-ui: $(FRONTEND_DIAGNOSTIC_UI_SRC) $(FRONTEND_SHARED_SRC) $(ENVIRONMENT_FILE) frontend/dist/raw/diagnostic-ui: $(FRONTEND_DIAGNOSTIC_UI_SRC) $(FRONTEND_SHARED_SRC)
npm --prefix frontend run build:dui npm --prefix frontend run build:dui
frontend/dist/raw/install-wizard: $(FRONTEND_INSTALL_WIZARD_SRC) $(FRONTEND_SHARED_SRC) $(ENVIRONMENT_FILE) frontend/dist/raw/install-wizard: $(FRONTEND_INSTALL_WIZARD_SRC) $(FRONTEND_SHARED_SRC)
npm --prefix frontend run build:install-wiz npm --prefix frontend run build:install-wiz
frontend/dist/static: $(EMBASSY_UIS) frontend/dist/static: $(EMBASSY_UIS) $(ENVIRONMENT_FILE)
./compress-uis.sh ./compress-uis.sh
frontend/config.json: $(GIT_HASH_FILE) frontend/config-sample.json frontend/config.json: $(GIT_HASH_FILE) frontend/config-sample.json
jq '.useMocks = false' frontend/config-sample.json > frontend/config.json jq '.useMocks = false' frontend/config-sample.json | jq '.gitHash = "$(shell cat GIT_HASH.txt)"' > frontend/config.json
jq '.packageArch = "$(ARCH)"' frontend/config.json > frontend/config.json.tmp
jq '.osArch = "$(OS_ARCH)"' frontend/config.json.tmp > frontend/config.json
rm frontend/config.json.tmp
npm --prefix frontend run-script build-config
frontend/patchdb-ui-seed.json: frontend/package.json frontend/patchdb-ui-seed.json: frontend/package.json
jq '."ack-welcome" = $(shell yq '.version' frontend/package.json)' frontend/patchdb-ui-seed.json > ui-seed.tmp jq '."ack-welcome" = $(shell jq '.version' frontend/package.json)' frontend/patchdb-ui-seed.json > ui-seed.tmp
mv ui-seed.tmp frontend/patchdb-ui-seed.json mv ui-seed.tmp frontend/patchdb-ui-seed.json
patch-db/client/node_modules: patch-db/client/package.json patch-db/client/node_modules: patch-db/client/package.json
@@ -186,7 +214,7 @@ patch-db/client/dist: $(PATCH_DB_CLIENT_SRC) patch-db/client/node_modules
npm --prefix frontend run build:deps npm --prefix frontend run build:deps
# used by github actions # used by github actions
backend-$(ARCH).tar: $(EMBASSY_BINS) compiled-$(ARCH).tar: $(COMPILED_TARGETS) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE)
tar -cvf $@ $^ tar -cvf $@ $^
# this is a convenience step to build all frontends - it is not referenced elsewhere in this file # this is a convenience step to build all frontends - it is not referenced elsewhere in this file
@@ -195,10 +223,7 @@ frontends: $(EMBASSY_UIS)
# this is a convenience step to build the UI # this is a convenience step to build the UI
ui: frontend/dist/raw/ui ui: frontend/dist/raw/ui
# used by github actions cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep:
backend: $(EMBASSY_BINS)
cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep: | sudo
ARCH=aarch64 ./build-cargo-dep.sh pi-beep ARCH=aarch64 ./build-cargo-dep.sh pi-beep
cargo-deps/$(ARCH)-unknown-linux-gnu/release/tokio-console: | sudo cargo-deps/$(ARCH)-unknown-linux-gnu/release/tokio-console: | sudo

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM network_keys WHERE package = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "b203820ee1c553a4b246eac74b79bd10d5717b2a0ddecf22330b7d531aac7c5d"
}

24
backend/Cargo.lock generated
View File

@@ -1906,28 +1906,6 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]]
name = "git-version"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b0decc02f4636b9ccad390dcbe77b722a77efedfa393caf8379a51d5c61899"
dependencies = [
"git-version-macro",
"proc-macro-hack",
]
[[package]]
name = "git-version-macro"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe69f1cbdb6e28af2bac214e943b99ce8a0a06b447d15d3e61161b0423139f3f"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.1" version = "0.3.1"
@@ -4970,12 +4948,12 @@ dependencies = [
"digest 0.10.7", "digest 0.10.7",
"divrem", "divrem",
"ed25519 2.2.3", "ed25519 2.2.3",
"ed25519-dalek 1.0.1",
"ed25519-dalek 2.0.0", "ed25519-dalek 2.0.0",
"embassy_container_init", "embassy_container_init",
"emver", "emver",
"fd-lock-rs", "fd-lock-rs",
"futures", "futures",
"git-version",
"gpt", "gpt",
"helpers", "helpers",
"hex", "hex",

View File

@@ -15,6 +15,7 @@ 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.3.5" version = "0.3.5"
license = "MIT"
[lib] [lib]
name = "startos" name = "startos"
@@ -31,7 +32,7 @@ cli = []
daemon = [] daemon = []
default = ["cli", "sdk", "daemon", "js_engine"] default = ["cli", "sdk", "daemon", "js_engine"]
dev = [] dev = []
podman = [] docker = []
sdk = [] sdk = []
unstable = ["console-subscriber", "tokio/tracing"] unstable = ["console-subscriber", "tokio/tracing"]
@@ -66,18 +67,17 @@ divrem = "1.0.0"
ed25519 = { version = "2.2.3", features = ["pkcs8", "pem", "alloc"] } ed25519 = { version = "2.2.3", features = ["pkcs8", "pem", "alloc"] }
ed25519-dalek = { version = "2.0.0", features = [ ed25519-dalek = { version = "2.0.0", features = [
"serde", "serde",
"hazmat",
"zeroize", "zeroize",
"rand_core", "rand_core",
"digest", "digest",
] } ] }
ed25519-dalek-v1 = { package = "ed25519-dalek", version = "1" }
embassy_container_init = { path = "../libs/embassy_container_init" } embassy_container_init = { path = "../libs/embassy_container_init" }
emver = { version = "0.1.7", git = "https://github.com/Start9Labs/emver-rs.git", features = [ emver = { version = "0.1.7", git = "https://github.com/Start9Labs/emver-rs.git", features = [
"serde", "serde",
] } ] }
fd-lock-rs = "0.1.4" fd-lock-rs = "0.1.4"
futures = "0.3.28" futures = "0.3.28"
git-version = "0.3.5"
gpt = "3.1.0" gpt = "3.1.0"
helpers = { path = "../libs/helpers" } helpers = { path = "../libs/helpers" }
hex = "0.4.3" hex = "0.4.3"

View File

@@ -3,11 +3,6 @@
set -e set -e
shopt -s expand_aliases shopt -s expand_aliases
if [ -z "$OS_ARCH" ]; then
>&2 echo '$OS_ARCH is required'
exit 1
fi
if [ -z "$ARCH" ]; then if [ -z "$ARCH" ]; then
ARCH=$(uname -m) ARCH=$(uname -m)
fi fi
@@ -23,27 +18,17 @@ if tty -s; then
fi fi
cd .. cd ..
FLAGS="" FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
RUSTFLAGS="" RUSTFLAGS=""
if [[ "$ENVIRONMENT" =~ (^|-)podman($|-) ]]; then
FLAGS="podman,$FLAGS"
fi
if [[ "$ENVIRONMENT" =~ (^|-)unstable($|-) ]]; then
FLAGS="unstable,$FLAGS"
RUSTFLAGS="$RUSTFLAGS --cfg tokio_unstable"
fi
if [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
FLAGS="dev,$FLAGS"
fi
alias 'rust-gnu-builder'='docker run $USE_TTY --rm -e "OS_ARCH=$OS_ARCH" -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/usr/local/cargo/registry -v "$(pwd)":/home/rust/src -w /home/rust/src -P start9/rust-arm-cross:aarch64' alias 'rust-gnu-builder'='docker run $USE_TTY --rm -e "RUSTFLAGS=$RUSTFLAGS" -v "$HOME/.cargo/registry":/usr/local/cargo/registry -v "$(pwd)":/home/rust/src -w /home/rust/src -P start9/rust-arm-cross:aarch64'
alias 'rust-musl-builder'='docker run $USE_TTY --rm -e "OS_ARCH=$OS_ARCH" -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P messense/rust-musl-cross:$ARCH-musl' alias 'rust-musl-builder'='docker run $USE_TTY --rm -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
set +e set +e
fail= fail=
echo "FLAGS=\"$FLAGS\"" echo "FEATURES=\"$FEATURES\""
echo "RUSTFLAGS=\"$RUSTFLAGS\"" echo "RUSTFLAGS=\"$RUSTFLAGS\""
rust-gnu-builder sh -c "(cd backend && cargo build --release --features avahi-alias,$FLAGS --locked --target=$ARCH-unknown-linux-gnu)" rust-gnu-builder sh -c "(cd backend && cargo build --release --features avahi-alias,$FEATURES --locked --target=$ARCH-unknown-linux-gnu)"
if test $? -ne 0; then if test $? -ne 0; then
fail=true fail=true
fi fi

View File

@@ -11,8 +11,8 @@ fi
frontend="../frontend/dist/static" frontend="../frontend/dist/static"
[ -d "$frontend" ] || mkdir -p "$frontend" [ -d "$frontend" ] || mkdir -p "$frontend"
if [ -z "$OS_ARCH" ]; then if [ -z "$PLATFORM" ]; then
export OS_ARCH=$(uname -m) export PLATFORM=$(uname -m)
fi fi
cargo install --path=. --no-default-features --features=js_engine,sdk,cli --locked cargo install --path=. --no-default-features --features=js_engine,sdk,cli --locked

View File

@@ -1,4 +1,5 @@
use digest::Digest; use std::time::SystemTime;
use ed25519_dalek::SecretKey; use ed25519_dalek::SecretKey;
use openssl::pkey::{PKey, Private}; use openssl::pkey::{PKey, Private};
use openssl::x509::X509; use openssl::x509::X509;
@@ -14,7 +15,7 @@ fn hash_password(password: &str) -> Result<String, Error> {
argon2::hash_encoded( argon2::hash_encoded(
password.as_bytes(), password.as_bytes(),
&rand::random::<[u8; 16]>()[..], &rand::random::<[u8; 16]>()[..],
&argon2::Config::default(), &argon2::Config::rfc9106_low_mem(),
) )
.with_kind(crate::ErrorKind::PasswordHashGeneration) .with_kind(crate::ErrorKind::PasswordHashGeneration)
} }
@@ -29,11 +30,11 @@ pub struct AccountInfo {
pub root_ca_cert: X509, pub root_ca_cert: X509,
} }
impl AccountInfo { impl AccountInfo {
pub fn new(password: &str) -> 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 root_ca_key = generate_key()?; let root_ca_key = generate_key()?;
let root_ca_cert = make_root_cert(&root_ca_key, &hostname)?; let root_ca_cert = make_root_cert(&root_ca_key, &hostname, start_time)?;
Ok(Self { Ok(Self {
server_id, server_id,
hostname, hostname,

View File

@@ -84,7 +84,7 @@ fn gen_pwd() {
argon2::hash_encoded( argon2::hash_encoded(
b"testing1234", b"testing1234",
&rand::random::<[u8; 16]>()[..], &rand::random::<[u8; 16]>()[..],
&argon2::Config::default() &argon2::Config::rfc9106_low_mem()
) )
.unwrap() .unwrap()
) )

View File

@@ -189,7 +189,7 @@ pub async fn recover_full_embassy(
os_backup.account.password = argon2::hash_encoded( os_backup.account.password = argon2::hash_encoded(
embassy_password.as_bytes(), embassy_password.as_bytes(),
&rand::random::<[u8; 16]>()[..], &rand::random::<[u8; 16]>()[..],
&argon2::Config::default(), &argon2::Config::rfc9106_low_mem(),
) )
.with_kind(ErrorKind::PasswordHashGeneration)?; .with_kind(ErrorKind::PasswordHashGeneration)?;

View File

@@ -1,13 +1,9 @@
use clap::Arg;
use rpc_toolkit::command;
use rpc_toolkit::run_cli;
use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::yajrc::RpcError;
use rpc_toolkit::{command, run_cli, Context};
use serde_json::Value; use serde_json::Value;
use crate::context::CliContext;
use crate::procedure::js_scripts::ExecuteArgs; use crate::procedure::js_scripts::ExecuteArgs;
use crate::s9pk::manifest::PackageId; use crate::s9pk::manifest::PackageId;
use crate::util::logger::EmbassyLogger;
use crate::util::serde::{display_serializable, parse_stdin_deserializable}; use crate::util::serde::{display_serializable, parse_stdin_deserializable};
use crate::version::{Current, VersionT}; use crate::version::{Current, VersionT};
use crate::Error; use crate::Error;
@@ -16,6 +12,9 @@ lazy_static::lazy_static! {
static ref VERSION_STRING: String = Current::new().semver().to_string(); static ref VERSION_STRING: String = Current::new().semver().to_string();
} }
struct DenoContext;
impl Context for DenoContext {}
#[command(subcommands(execute, sandbox))] #[command(subcommands(execute, sandbox))]
fn deno_api() -> Result<(), Error> { fn deno_api() -> Result<(), Error> {
Ok(()) Ok(())
@@ -70,13 +69,11 @@ impl PackageLogger {
use tracing_subscriber::prelude::*; use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter}; use tracing_subscriber::{fmt, EnvFilter};
let filter_layer = EnvFilter::builder() let filter_layer = EnvFilter::default().add_directive(
.with_default_directive( format!("{}=warn", std::module_path!().split("::").next().unwrap())
format!("{}=info", std::module_path!().split("::").next().unwrap()) .parse()
.parse() .unwrap(),
.unwrap(), );
)
.from_env_lossy();
let fmt_layer = fmt::layer().with_writer(std::io::stderr).with_target(true); let fmt_layer = fmt::layer().with_writer(std::io::stderr).with_target(true);
let journald_layer = tracing_journald::layer() let journald_layer = tracing_journald::layer()
.unwrap() .unwrap()
@@ -103,16 +100,8 @@ fn inner_main() -> Result<(), Error> {
command: deno_api, command: deno_api,
app: app => app app: app => app
.name("StartOS Deno Executor") .name("StartOS Deno Executor")
.version(&**VERSION_STRING) .version(&**VERSION_STRING),
.arg( context: _m => DenoContext,
clap::Arg::with_name("config")
.short('c')
.long("config")
.takes_value(true),
),
context: matches => {
CliContext::init(matches)?
},
exit: |e: RpcError| { exit: |e: RpcError| {
match e.data { match e.data {
Some(Value::String(s)) => eprintln!("{}: {}", e.message, s), Some(Value::String(s)) => eprintln!("{}: {}", e.message, s),

View File

@@ -17,7 +17,7 @@ use crate::net::web_server::WebServer;
use crate::shutdown::Shutdown; use crate::shutdown::Shutdown;
use crate::sound::CHIME; use crate::sound::CHIME;
use crate::util::Invoke; use crate::util::Invoke;
use crate::{Error, ErrorKind, ResultExt, OS_ARCH}; use crate::{Error, ErrorKind, ResultExt, PLATFORM};
#[instrument(skip_all)] #[instrument(skip_all)]
async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> { async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> {
@@ -30,19 +30,19 @@ async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Er
Command::new("ln") Command::new("ln")
.arg("-sf") .arg("-sf")
.arg("/usr/lib/embassy/scripts/fake-apt") .arg("/usr/lib/startos/scripts/fake-apt")
.arg("/usr/local/bin/apt") .arg("/usr/local/bin/apt")
.invoke(crate::ErrorKind::OpenSsh) .invoke(crate::ErrorKind::OpenSsh)
.await?; .await?;
Command::new("ln") Command::new("ln")
.arg("-sf") .arg("-sf")
.arg("/usr/lib/embassy/scripts/fake-apt") .arg("/usr/lib/startos/scripts/fake-apt")
.arg("/usr/local/bin/apt-get") .arg("/usr/local/bin/apt-get")
.invoke(crate::ErrorKind::OpenSsh) .invoke(crate::ErrorKind::OpenSsh)
.await?; .await?;
Command::new("ln") Command::new("ln")
.arg("-sf") .arg("-sf")
.arg("/usr/lib/embassy/scripts/fake-apt") .arg("/usr/lib/startos/scripts/fake-apt")
.arg("/usr/local/bin/aptitude") .arg("/usr/local/bin/aptitude")
.invoke(crate::ErrorKind::OpenSsh) .invoke(crate::ErrorKind::OpenSsh)
.await?; .await?;
@@ -177,7 +177,7 @@ async fn run_script_if_exists<P: AsRef<Path>>(path: P) {
#[instrument(skip_all)] #[instrument(skip_all)]
async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> { async fn inner_main(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> {
if OS_ARCH == "raspberrypi" && tokio::fs::metadata(STANDBY_MODE_PATH).await.is_ok() { if &*PLATFORM == "raspberrypi" && tokio::fs::metadata(STANDBY_MODE_PATH).await.is_ok() {
tokio::fs::remove_file(STANDBY_MODE_PATH).await?; tokio::fs::remove_file(STANDBY_MODE_PATH).await?;
Command::new("sync").invoke(ErrorKind::Filesystem).await?; Command::new("sync").invoke(ErrorKind::Filesystem).await?;
crate::sound::SHUTDOWN.play().await?; crate::sound::SHUTDOWN.play().await?;

View File

@@ -14,9 +14,8 @@ use rpc_toolkit::command;
use tracing::instrument; use tracing::instrument;
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::prelude::*; use crate::prelude::*;
use crate::s9pk::manifest::{PackageId}; use crate::s9pk::manifest::PackageId;
use crate::util::display_none; use crate::util::display_none;
use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat}; use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat};
use crate::Error; use crate::Error;

View File

@@ -1,4 +1,4 @@
use std::borrow::{Borrow, Cow}; use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::fmt; use std::fmt;
use std::fmt::Debug; use std::fmt::Debug;
@@ -105,7 +105,7 @@ where
rng: &mut R, rng: &mut R,
timeout: &Option<Duration>, timeout: &Option<Duration>,
) -> Result<Value, Self::Error> { ) -> Result<Value, Self::Error> {
self.gen_with(self.default_spec().borrow(), rng, timeout) self.gen_with(self.default_spec(), rng, timeout)
} }
} }

View File

@@ -15,6 +15,7 @@ use serde::Deserialize;
use sqlx::postgres::PgConnectOptions; use sqlx::postgres::PgConnectOptions;
use sqlx::PgPool; use sqlx::PgPool;
use tokio::sync::{broadcast, oneshot, Mutex, RwLock}; use tokio::sync::{broadcast, oneshot, Mutex, RwLock};
use tokio::time::Instant;
use tracing::instrument; use tracing::instrument;
use super::setup::CURRENT_SECRET; use super::setup::CURRENT_SECRET;
@@ -29,7 +30,7 @@ use crate::install::cleanup::{cleanup_failed, uninstall};
use crate::manager::ManagerMap; use crate::manager::ManagerMap;
use crate::middleware::auth::HashSessionToken; use crate::middleware::auth::HashSessionToken;
use crate::net::net_controller::NetController; use crate::net::net_controller::NetController;
use crate::net::ssl::SslManager; use crate::net::ssl::{root_ca_start_time, SslManager};
use crate::net::wifi::WpaCli; use crate::net::wifi::WpaCli;
use crate::notifications::NotificationManager; use crate::notifications::NotificationManager;
use crate::shutdown::Shutdown; use crate::shutdown::Shutdown;
@@ -123,6 +124,7 @@ pub struct RpcContextSeed {
pub current_secret: Arc<Jwk>, pub current_secret: Arc<Jwk>,
pub client: Client, pub client: Client,
pub hardware: Hardware, pub hardware: Hardware,
pub start_time: Instant,
} }
pub struct Hardware { pub struct Hardware {
@@ -158,7 +160,7 @@ impl RpcContext {
base.dns_bind base.dns_bind
.as_deref() .as_deref()
.unwrap_or(&[SocketAddr::from(([127, 0, 0, 1], 53))]), .unwrap_or(&[SocketAddr::from(([127, 0, 0, 1], 53))]),
SslManager::new(&account)?, SslManager::new(&account, root_ca_start_time().await?)?,
&account.hostname, &account.hostname,
&account.key, &account.key,
) )
@@ -214,6 +216,7 @@ impl RpcContext {
.build() .build()
.with_kind(crate::ErrorKind::ParseUrl)?, .with_kind(crate::ErrorKind::ParseUrl)?,
hardware: Hardware { devices, ram }, hardware: Hardware { devices, ram },
start_time: Instant::now(),
}); });
let res = Self(seed.clone()); let res = Self(seed.clone());

View File

@@ -4,6 +4,7 @@ use std::sync::Arc;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use emver::VersionRange; use emver::VersionRange;
use imbl_value::InternedString;
use ipnet::{Ipv4Net, Ipv6Net}; use ipnet::{Ipv4Net, Ipv6Net};
use isocountry::CountryCode; use isocountry::CountryCode;
use itertools::Itertools; use itertools::Itertools;
@@ -24,6 +25,7 @@ use crate::s9pk::manifest::{Manifest, PackageId};
use crate::status::Status; use crate::status::Status;
use crate::util::Version; use crate::util::Version;
use crate::version::{Current, VersionT}; use crate::version::{Current, VersionT};
use crate::{ARCH, PLATFORM};
#[derive(Debug, Deserialize, Serialize, HasModel)] #[derive(Debug, Deserialize, Serialize, HasModel)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
@@ -40,6 +42,8 @@ impl Database {
let lan_address = account.hostname.lan_address().parse().unwrap(); let lan_address = account.hostname.lan_address().parse().unwrap();
Database { Database {
server_info: ServerInfo { server_info: ServerInfo {
arch: get_arch(),
platform: get_platform(),
id: account.server_id.clone(), id: account.server_id.clone(),
version: Current::new().semver().into(), version: Current::new().semver().into(),
hostname: account.hostname.no_dot_host_name(), hostname: account.hostname.no_dot_host_name(),
@@ -55,6 +59,8 @@ impl Database {
backup_progress: None, backup_progress: None,
updated: false, updated: false,
update_progress: None, update_progress: None,
shutting_down: false,
restarting: false,
}, },
wifi: WifiInfo { wifi: WifiInfo {
ssids: Vec::new(), ssids: Vec::new(),
@@ -77,8 +83,8 @@ impl Database {
.iter() .iter()
.map(|x| format!("{x:X}")) .map(|x| format!("{x:X}"))
.join(":"), .join(":"),
system_start_time: Utc::now().to_rfc3339(), ntp_synced: false,
zram: false, zram: true,
}, },
package_data: AllPackageData::default(), package_data: AllPackageData::default(),
lan_port_forwards: LanPortForwards::new(), lan_port_forwards: LanPortForwards::new(),
@@ -90,10 +96,22 @@ impl Database {
pub type DatabaseModel = Model<Database>; pub type DatabaseModel = Model<Database>;
fn get_arch() -> InternedString {
(*ARCH).into()
}
fn get_platform() -> InternedString {
(&*PLATFORM).into()
}
#[derive(Debug, Deserialize, Serialize, HasModel)] #[derive(Debug, Deserialize, Serialize, HasModel)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
#[model = "Model<Self>"] #[model = "Model<Self>"]
pub struct ServerInfo { pub struct ServerInfo {
#[serde(default = "get_arch")]
pub arch: InternedString,
#[serde(default = "get_platform")]
pub platform: InternedString,
pub id: String, pub id: String,
pub hostname: String, pub hostname: String,
pub version: Version, pub version: Version,
@@ -112,7 +130,8 @@ pub struct ServerInfo {
pub password_hash: String, pub password_hash: String,
pub pubkey: String, pub pubkey: String,
pub ca_fingerprint: String, pub ca_fingerprint: String,
pub system_start_time: String, #[serde(default)]
pub ntp_synced: bool,
#[serde(default)] #[serde(default)]
pub zram: bool, pub zram: bool,
} }
@@ -152,6 +171,10 @@ pub struct ServerStatus {
pub backup_progress: Option<BTreeMap<PackageId, BackupProgress>>, pub backup_progress: Option<BTreeMap<PackageId, BackupProgress>>,
pub updated: bool, pub updated: bool,
pub update_progress: Option<UpdateProgress>, pub update_progress: Option<UpdateProgress>,
#[serde(default)]
pub shutting_down: bool,
#[serde(default)]
pub restarting: bool,
} }
#[derive(Debug, Deserialize, Serialize, HasModel)] #[derive(Debug, Deserialize, Serialize, HasModel)]

View File

@@ -23,6 +23,7 @@ pub async fn btrfs_check_repair(logicalname: impl AsRef<Path>) -> Result<Require
Command::new("btrfs") Command::new("btrfs")
.arg("check") .arg("check")
.arg("--repair") .arg("--repair")
.arg("--force")
.arg(logicalname.as_ref()) .arg(logicalname.as_ref())
.invoke(crate::ErrorKind::DiskManagement) .invoke(crate::ErrorKind::DiskManagement)
.await?; .await?;

View File

@@ -84,7 +84,7 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
argon2::hash_encoded( argon2::hash_encoded(
password.as_bytes(), password.as_bytes(),
&rand::random::<[u8; 16]>()[..], &rand::random::<[u8; 16]>()[..],
&argon2::Config::default(), &argon2::Config::rfc9106_low_mem(),
) )
.with_kind(crate::ErrorKind::PasswordHashGeneration)?, .with_kind(crate::ErrorKind::PasswordHashGeneration)?,
); );
@@ -134,7 +134,7 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
argon2::hash_encoded( argon2::hash_encoded(
new_password.as_bytes(), new_password.as_bytes(),
&rand::random::<[u8; 16]>()[..], &rand::random::<[u8; 16]>()[..],
&argon2::Config::default(), &argon2::Config::rfc9106_low_mem(),
) )
.with_kind(crate::ErrorKind::PasswordHashGeneration)?, .with_kind(crate::ErrorKind::PasswordHashGeneration)?,
); );

View File

@@ -2,13 +2,12 @@ use std::os::unix::ffi::OsStrExt;
use std::path::Path; use std::path::Path;
use async_trait::async_trait; use async_trait::async_trait;
use color_eyre::eyre::eyre;
use digest::generic_array::GenericArray; use digest::generic_array::GenericArray;
use digest::{Digest, OutputSizeUser}; use digest::{Digest, OutputSizeUser};
use sha2::Sha256; use sha2::Sha256;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use super::{FileSystem, MountType}; use super::{FileSystem, MountType};
use crate::util::Invoke;
use crate::{Error, ResultExt}; use crate::{Error, ResultExt};
pub async fn mount_ecryptfs<P0: AsRef<Path>, P1: AsRef<Path>>( pub async fn mount_ecryptfs<P0: AsRef<Path>, P1: AsRef<Path>>(
@@ -17,7 +16,7 @@ pub async fn mount_ecryptfs<P0: AsRef<Path>, P1: AsRef<Path>>(
key: &str, key: &str,
) -> Result<(), Error> { ) -> Result<(), Error> {
tokio::fs::create_dir_all(dst.as_ref()).await?; tokio::fs::create_dir_all(dst.as_ref()).await?;
let mut ecryptfs = tokio::process::Command::new("mount") tokio::process::Command::new("mount")
.arg("-t") .arg("-t")
.arg("ecryptfs") .arg("ecryptfs")
.arg(src.as_ref()) .arg(src.as_ref())
@@ -25,22 +24,9 @@ pub async fn mount_ecryptfs<P0: AsRef<Path>, P1: AsRef<Path>>(
.arg("-o") .arg("-o")
// for more information `man ecryptfs` // for more information `man ecryptfs`
.arg(format!("key=passphrase:passphrase_passwd={},ecryptfs_cipher=aes,ecryptfs_key_bytes=32,ecryptfs_passthrough=n,ecryptfs_enable_filename_crypto=y,no_sig_cache", key)) .arg(format!("key=passphrase:passphrase_passwd={},ecryptfs_cipher=aes,ecryptfs_key_bytes=32,ecryptfs_passthrough=n,ecryptfs_enable_filename_crypto=y,no_sig_cache", key))
.stdin(std::process::Stdio::piped()) .input(Some(&mut std::io::Cursor::new(b"\n")))
.stderr(std::process::Stdio::piped()) .invoke(crate::ErrorKind::Filesystem).await?;
.spawn()?; Ok(())
let mut stdin = ecryptfs.stdin.take().unwrap();
let mut stderr = ecryptfs.stderr.take().unwrap();
stdin.write_all(b"\n").await?;
stdin.flush().await?;
stdin.shutdown().await?;
drop(stdin);
let mut err = String::new();
stderr.read_to_string(&mut err).await?;
if !ecryptfs.wait().await?.success() {
Err(Error::new(eyre!("{}", err), crate::ErrorKind::Filesystem))
} else {
Ok(())
}
} }
pub struct EcryptFS<EncryptedDir: AsRef<Path>, Key: AsRef<str>> { pub struct EcryptFS<EncryptedDir: AsRef<Path>, Key: AsRef<str>> {

View File

@@ -1,9 +1,8 @@
use std::path::Path; use std::path::Path;
use std::process::Stdio;
use async_compression::tokio::bufread::GzipDecoder; use async_compression::tokio::bufread::GzipDecoder;
use tokio::fs::File; use tokio::fs::File;
use tokio::io::{AsyncRead, AsyncWriteExt, BufReader}; use tokio::io::{AsyncRead, BufReader};
use tokio::process::Command; use tokio::process::Command;
use crate::disk::fsck::RequiresReboot; use crate::disk::fsck::RequiresReboot;
@@ -23,7 +22,7 @@ pub async fn update_firmware() -> Result<RequiresReboot, Error> {
if product_name.is_empty() { if product_name.is_empty() {
return Ok(RequiresReboot(false)); return Ok(RequiresReboot(false));
} }
let firmware_dir = Path::new("/usr/lib/embassy/firmware").join(&product_name); let firmware_dir = Path::new("/usr/lib/startos/firmware").join(&product_name);
if tokio::fs::metadata(&firmware_dir).await.is_ok() { if tokio::fs::metadata(&firmware_dir).await.is_ok() {
let current_firmware = String::from_utf8( let current_firmware = String::from_utf8(
Command::new("dmidecode") Command::new("dmidecode")
@@ -44,36 +43,25 @@ pub async fn update_firmware() -> Result<RequiresReboot, Error> {
let mut firmware_read_dir = tokio::fs::read_dir(&firmware_dir).await?; let mut firmware_read_dir = tokio::fs::read_dir(&firmware_dir).await?;
while let Some(entry) = firmware_read_dir.next_entry().await? { while let Some(entry) = firmware_read_dir.next_entry().await? {
let filename = entry.file_name().to_string_lossy().into_owned(); let filename = entry.file_name().to_string_lossy().into_owned();
let rdr: Option<Box<dyn AsyncRead + Unpin>> = if filename.ends_with(".rom.gz") { let rdr: Option<Box<dyn AsyncRead + Unpin + Send>> =
Some(Box::new(GzipDecoder::new(BufReader::new( if filename.ends_with(".rom.gz") {
File::open(entry.path()).await?, Some(Box::new(GzipDecoder::new(BufReader::new(
)))) File::open(entry.path()).await?,
} else if filename.ends_with(".rom") { ))))
Some(Box::new(File::open(entry.path()).await?)) } else if filename.ends_with(".rom") {
} else { Some(Box::new(File::open(entry.path()).await?))
None } else {
}; None
};
if let Some(mut rdr) = rdr { if let Some(mut rdr) = rdr {
let mut flashrom = Command::new("flashrom") Command::new("flashrom")
.arg("-p") .arg("-p")
.arg("internal") .arg("internal")
.arg("-w-") .arg("-w-")
.stdin(Stdio::piped()) .input(Some(&mut rdr))
.spawn()?; .invoke(ErrorKind::Firmware)
let mut rom_dest = flashrom.stdin.take().or_not_found("stdin")?; .await?;
tokio::io::copy(&mut rdr, &mut rom_dest).await?; return Ok(RequiresReboot(true));
rom_dest.flush().await?;
rom_dest.shutdown().await?;
drop(rom_dest);
let o = flashrom.wait_with_output().await?;
if !o.status.success() {
return Err(Error::new(
eyre!("{}", std::str::from_utf8(&o.stderr)?),
ErrorKind::Firmware,
));
} else {
return Ok(RequiresReboot(true));
}
} }
} }
} }

View File

@@ -1,7 +1,7 @@
use std::fs::Permissions; use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
use std::path::Path; use std::path::Path;
use std::time::Duration; use std::time::{Duration, SystemTime};
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use helpers::NonDetachingJoinHandle; use helpers::NonDetachingJoinHandle;
@@ -19,7 +19,6 @@ use crate::install::PKG_ARCHIVE_DIR;
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH; use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
use crate::prelude::*; use crate::prelude::*;
use crate::sound::BEP; use crate::sound::BEP;
use crate::system::time;
use crate::util::cpupower::{ use crate::util::cpupower::{
current_governor, get_available_governors, set_governor, GOVERNOR_PERFORMANCE, current_governor, get_available_governors, set_governor, GOVERNOR_PERFORMANCE,
}; };
@@ -255,6 +254,17 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
} }
} }
crate::disk::mount::util::bind(&log_dir, "/var/log/journal", false).await?; crate::disk::mount::util::bind(&log_dir, "/var/log/journal", false).await?;
match Command::new("chattr")
.arg("-R")
.arg("+C")
.arg("/var/log/journal")
.invoke(ErrorKind::Filesystem)
.await
{
Ok(_) => Ok(()),
Err(e) if e.source.to_string().contains("Operation not supported") => Ok(()),
Err(e) => Err(e),
}?;
Command::new("systemctl") Command::new("systemctl")
.arg("restart") .arg("restart")
.arg("systemd-journald") .arg("systemd-journald")
@@ -263,6 +273,9 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
tracing::info!("Mounted Logs"); tracing::info!("Mounted Logs");
let tmp_dir = cfg.datadir().join("package-data/tmp"); let tmp_dir = cfg.datadir().join("package-data/tmp");
if should_rebuild && tokio::fs::metadata(&tmp_dir).await.is_ok() {
tokio::fs::remove_dir_all(&tmp_dir).await?;
}
if tokio::fs::metadata(&tmp_dir).await.is_err() { if tokio::fs::metadata(&tmp_dir).await.is_err() {
tokio::fs::create_dir_all(&tmp_dir).await?; tokio::fs::create_dir_all(&tmp_dir).await?;
} }
@@ -275,9 +288,6 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
.datadir() .datadir()
.join(format!("package-data/tmp/{CONTAINER_TOOL}")); .join(format!("package-data/tmp/{CONTAINER_TOOL}"));
let tmp_docker_exists = tokio::fs::metadata(&tmp_docker).await.is_ok(); let tmp_docker_exists = tokio::fs::metadata(&tmp_docker).await.is_ok();
if should_rebuild && tmp_docker_exists {
tokio::fs::remove_dir_all(&tmp_docker).await?;
}
if CONTAINER_TOOL == "docker" { if CONTAINER_TOOL == "docker" {
Command::new("systemctl") Command::new("systemctl")
.arg("stop") .arg("stop")
@@ -309,7 +319,7 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
} }
tracing::info!("Loading System Docker Images"); tracing::info!("Loading System Docker Images");
crate::install::load_images("/usr/lib/embassy/system-images").await?; crate::install::load_images("/usr/lib/startos/system-images").await?;
tracing::info!("Loaded System Docker Images"); tracing::info!("Loaded System Docker Images");
tracing::info!("Loading Package Docker Images"); tracing::info!("Loading Package Docker Images");
@@ -361,15 +371,28 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
} }
} }
let mut warn_time_not_synced = true; let mut time_not_synced = true;
for _ in 0..60 { let mut not_made_progress = 0u32;
for _ in 0..1800 {
if check_time_is_synchronized().await? { if check_time_is_synchronized().await? {
warn_time_not_synced = false; time_not_synced = false;
break; break;
} }
let t = SystemTime::now();
tokio::time::sleep(Duration::from_secs(1)).await; tokio::time::sleep(Duration::from_secs(1)).await;
if t.elapsed()
.map(|t| t > Duration::from_secs_f64(1.1))
.unwrap_or(true)
{
not_made_progress = 0;
} else {
not_made_progress += 1;
}
if not_made_progress > 30 {
break;
}
} }
if warn_time_not_synced { if time_not_synced {
tracing::warn!("Timed out waiting for system time to synchronize"); tracing::warn!("Timed out waiting for system time to synchronize");
} else { } else {
tracing::info!("Syncronized system clock"); tracing::info!("Syncronized system clock");
@@ -383,9 +406,24 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
updated: false, updated: false,
update_progress: None, update_progress: None,
backup_progress: None, backup_progress: None,
shutting_down: false,
restarting: false,
}; };
server_info.system_start_time = time().await?; server_info.ntp_synced = if time_not_synced {
let db = db.clone();
tokio::spawn(async move {
while !check_time_is_synchronized().await.unwrap() {
tokio::time::sleep(Duration::from_secs(30)).await;
}
db.mutate(|v| v.as_server_info_mut().as_ntp_synced_mut().ser(&true))
.await
.unwrap()
});
false
} else {
true
};
db.mutate(|v| { db.mutate(|v| {
v.as_server_info_mut().ser(&server_info)?; v.as_server_info_mut().ser(&server_info)?;

View File

@@ -173,7 +173,7 @@ where
); );
cleanup(ctx, id, &version).await?; cleanup(ctx, id, &version).await?;
cleanup_folder(volume_dir, Arc::new(dependents_paths)).await; cleanup_folder(volume_dir, Arc::new(dependents_paths)).await;
remove_tor_keys(secrets, id).await?; remove_network_keys(secrets, id).await?;
ctx.db ctx.db
.mutate(|d| { .mutate(|d| {
@@ -188,12 +188,15 @@ where
} }
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn remove_tor_keys<Ex>(secrets: &mut Ex, id: &PackageId) -> Result<(), Error> pub async fn remove_network_keys<Ex>(secrets: &mut Ex, id: &PackageId) -> Result<(), Error>
where where
for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, for<'a> &'a mut Ex: Executor<'a, Database = Postgres>,
{ {
sqlx::query!("DELETE FROM network_keys WHERE package = $1", &*id)
.execute(&mut *secrets)
.await?;
sqlx::query!("DELETE FROM tor WHERE package = $1", &*id) sqlx::query!("DELETE FROM tor WHERE package = $1", &*id)
.execute(secrets) .execute(&mut *secrets)
.await?; .await?;
Ok(()) Ok(())
} }

View File

@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
use std::io::SeekFrom; use std::io::SeekFrom;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@@ -49,9 +48,9 @@ use crate::s9pk::manifest::{Manifest, PackageId};
use crate::s9pk::reader::S9pkReader; use crate::s9pk::reader::S9pkReader;
use crate::status::{MainStatus, Status}; use crate::status::{MainStatus, Status};
use crate::util::docker::CONTAINER_TOOL; use crate::util::docker::CONTAINER_TOOL;
use crate::util::io::{copy_and_shutdown, response_to_reader}; use crate::util::io::response_to_reader;
use crate::util::serde::{display_serializable, Port}; use crate::util::serde::{display_serializable, Port};
use crate::util::{display_none, AsyncFileExt, Version}; use crate::util::{display_none, AsyncFileExt, Invoke, Version};
use crate::volume::{asset_dir, script_dir}; use crate::volume::{asset_dir, script_dir};
use crate::{Error, ErrorKind, ResultExt}; use crate::{Error, ErrorKind, ResultExt};
@@ -838,15 +837,15 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
None None
}; };
let icon_path = if let Some(marketplace_url) = &marketplace_url { let icon_path = if let Some(manifest) = &manifest {
if let Some(manifest) = &manifest { let dir = ctx
let dir = ctx .datadir
.datadir .join(PKG_PUBLIC_DIR)
.join(PKG_PUBLIC_DIR) .join(&manifest.id)
.join(&manifest.id) .join(manifest.version.as_str());
.join(manifest.version.as_str()); let icon_path = dir.join(format!("icon.{}", manifest.assets.icon_type()));
let icon_path = dir.join(format!("icon.{}", manifest.assets.icon_type())); if tokio::fs::metadata(&icon_path).await.is_err() {
if tokio::fs::metadata(&icon_path).await.is_err() { if let Some(marketplace_url) = &marketplace_url {
tokio::fs::create_dir_all(&dir).await?; tokio::fs::create_dir_all(&dir).await?;
let icon = ctx let icon = ctx
.client .client
@@ -864,10 +863,12 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
let mut dst = File::create(&icon_path).await?; let mut dst = File::create(&icon_path).await?;
tokio::io::copy(&mut response_to_reader(icon), &mut dst).await?; tokio::io::copy(&mut response_to_reader(icon), &mut dst).await?;
dst.sync_all().await?; dst.sync_all().await?;
Some(icon_path)
} else {
None
} }
Some(icon_path)
} else { } else {
None Some(icon_path)
} }
} else { } else {
None None
@@ -951,32 +952,11 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
tracing::info!("Install {}@{}: Unpacking Docker Images", pkg_id, version); tracing::info!("Install {}@{}: Unpacking Docker Images", pkg_id, version);
progress progress
.track_read_during(ctx.db.clone(), pkg_id, || async { .track_read_during(ctx.db.clone(), pkg_id, || async {
let mut load = Command::new(CONTAINER_TOOL) Command::new(CONTAINER_TOOL)
.arg("load") .arg("load")
.stdin(Stdio::piped()) .input(Some(&mut rdr.docker_images().await?))
.stderr(Stdio::piped()) .invoke(ErrorKind::Docker)
.spawn()?; .await
let load_in = load.stdin.take().ok_or_else(|| {
Error::new(
eyre!("Could not write to stdin of docker load"),
crate::ErrorKind::Docker,
)
})?;
let mut docker_rdr = rdr.docker_images().await?;
copy_and_shutdown(&mut docker_rdr, load_in).await?;
let res = load.wait_with_output().await?;
if !res.status.success() {
Err(Error::new(
eyre!(
"{}",
String::from_utf8(res.stderr)
.unwrap_or_else(|e| format!("Could not parse stderr: {}", e))
),
crate::ErrorKind::Docker,
))
} else {
Ok(())
}
}) })
.await?; .await?;
tracing::info!("Install {}@{}: Unpacked Docker Images", pkg_id, version,); tracing::info!("Install {}@{}: Unpacked Docker Images", pkg_id, version,);
@@ -1273,57 +1253,36 @@ pub fn load_images<'a, P: AsRef<Path> + 'a + Send + Sync>(
let path = entry.path(); let path = entry.path();
let ext = path.extension().and_then(|ext| ext.to_str()); let ext = path.extension().and_then(|ext| ext.to_str());
if ext == Some("tar") || ext == Some("s9pk") { if ext == Some("tar") || ext == Some("s9pk") {
let mut load = Command::new(CONTAINER_TOOL) if let Err(e) = async {
.arg("load") match ext {
.stdin(Stdio::piped()) Some("tar") => {
.stderr(Stdio::piped()) Command::new(CONTAINER_TOOL)
.spawn()?; .arg("load")
let load_in = load.stdin.take().ok_or_else(|| { .input(Some(&mut File::open(&path).await?))
Error::new( .invoke(ErrorKind::Docker)
eyre!("Could not write to stdin of docker load"), .await
crate::ErrorKind::Docker,
)
})?;
match ext {
Some("tar") => {
copy_and_shutdown(&mut File::open(&path).await?, load_in)
.await?
}
Some("s9pk") => match async {
let mut reader = S9pkReader::open(&path, true).await?;
copy_and_shutdown(&mut reader.docker_images().await?, load_in)
.await?;
Ok::<_, Error>(())
}
.await
{
Ok(()) => (),
Err(e) => {
tracing::error!(
"Error loading docker images from s9pk: {e}"
);
tracing::debug!("{e:?}");
return Ok(());
} }
}, Some("s9pk") => {
_ => unreachable!(), Command::new(CONTAINER_TOOL)
}; .arg("load")
.input(Some(
let res = load.wait_with_output().await?; &mut S9pkReader::open(&path, true)
if !res.status.success() { .await?
Err(Error::new( .docker_images()
eyre!( .await?,
"{}", ))
String::from_utf8(res.stderr).unwrap_or_else(|e| format!( .invoke(ErrorKind::Docker)
"Could not parse stderr: {}", .await
e }
)) _ => unreachable!(),
), }
crate::ErrorKind::Docker,
))
} else {
Ok(())
} }
.await
{
tracing::error!("Error loading docker images from s9pk: {e}");
tracing::debug!("{e:?}");
}
Ok(())
} else { } else {
Ok(()) Ok(())
} }

View File

@@ -5,12 +5,21 @@ pub const DEFAULT_MARKETPLACE: &str = "https://registry.start9.com";
pub const BUFFER_SIZE: usize = 1024; pub const BUFFER_SIZE: usize = 1024;
pub const HOST_IP: [u8; 4] = [172, 18, 0, 1]; pub const HOST_IP: [u8; 4] = [172, 18, 0, 1];
pub const TARGET: &str = current_platform::CURRENT_PLATFORM; pub const TARGET: &str = current_platform::CURRENT_PLATFORM;
pub const OS_ARCH: &str = env!("OS_ARCH");
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref ARCH: &'static str = { pub static ref ARCH: &'static str = {
let (arch, _) = TARGET.split_once("-").unwrap(); let (arch, _) = TARGET.split_once("-").unwrap();
arch arch
}; };
pub static ref PLATFORM: String = {
if let Ok(platform) = std::fs::read_to_string("/usr/lib/startos/PLATFORM.txt") {
platform
} else {
ARCH.to_string()
}
};
pub static ref SOURCE_DATE: SystemTime = {
std::fs::metadata(std::env::current_exe().unwrap()).unwrap().modified().unwrap()
};
} }
pub mod account; pub mod account;
@@ -56,6 +65,8 @@ pub mod util;
pub mod version; pub mod version;
pub mod volume; pub mod volume;
use std::time::SystemTime;
pub use config::Config; pub use config::Config;
pub use error::{Error, ErrorKind, ResultExt}; pub use error::{Error, ErrorKind, ResultExt};
use rpc_toolkit::command; use rpc_toolkit::command;

View File

@@ -136,7 +136,13 @@ pub struct LogEntry {
} }
impl std::fmt::Display for LogEntry { impl std::fmt::Display for LogEntry {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{} {}", self.timestamp, self.message) write!(
f,
"{} {}",
self.timestamp
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
self.message
)
} }
} }
@@ -145,7 +151,7 @@ pub struct JournalctlEntry {
#[serde(rename = "__REALTIME_TIMESTAMP")] #[serde(rename = "__REALTIME_TIMESTAMP")]
pub timestamp: String, pub timestamp: String,
#[serde(rename = "MESSAGE")] #[serde(rename = "MESSAGE")]
#[serde(deserialize_with = "deserialize_string_or_utf8_array")] #[serde(deserialize_with = "deserialize_log_message")]
pub message: String, pub message: String,
#[serde(rename = "__CURSOR")] #[serde(rename = "__CURSOR")]
pub cursor: String, pub cursor: String,
@@ -164,7 +170,7 @@ impl JournalctlEntry {
} }
} }
fn deserialize_string_or_utf8_array<'de, D: serde::de::Deserializer<'de>>( fn deserialize_log_message<'de, D: serde::de::Deserializer<'de>>(
deserializer: D, deserializer: D,
) -> std::result::Result<String, D::Error> { ) -> std::result::Result<String, D::Error> {
struct Visitor; struct Visitor;
@@ -177,13 +183,7 @@ fn deserialize_string_or_utf8_array<'de, D: serde::de::Deserializer<'de>>(
where where
E: serde::de::Error, E: serde::de::Error,
{ {
Ok(v.to_owned()) Ok(v.trim().to_owned())
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v)
} }
fn visit_unit<E>(self) -> Result<Self::Value, E> fn visit_unit<E>(self) -> Result<Self::Value, E>
where where
@@ -201,6 +201,7 @@ fn deserialize_string_or_utf8_array<'de, D: serde::de::Deserializer<'de>>(
.flatten() .flatten()
.collect::<Result<Vec<u8>, _>>()?, .collect::<Result<Vec<u8>, _>>()?,
) )
.map(|s| s.trim().to_owned())
.map_err(serde::de::Error::custom) .map_err(serde::de::Error::custom)
} }
} }
@@ -374,12 +375,12 @@ pub async fn journalctl(
cmd.arg(format!("_COMM={}", SYSTEM_UNIT)); cmd.arg(format!("_COMM={}", SYSTEM_UNIT));
} }
LogSource::Container(id) => { LogSource::Container(id) => {
#[cfg(feature = "podman")] #[cfg(not(feature = "docker"))]
cmd.arg(format!( cmd.arg(format!(
"SYSLOG_IDENTIFIER={}", "SYSLOG_IDENTIFIER={}",
DockerProcedure::container_name(&id, None) DockerProcedure::container_name(&id, None)
)); ));
#[cfg(not(feature = "podman"))] #[cfg(feature = "docker")]
cmd.arg(format!( cmd.arg(format!(
"CONTAINER_NAME={}", "CONTAINER_NAME={}",
DockerProcedure::container_name(&id, None) DockerProcedure::container_name(&id, None)

View File

@@ -1,6 +1,8 @@
use models::ErrorKind; use models::ErrorKind;
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::procedure::docker::DockerProcedure;
use crate::procedure::PackageProcedure;
use crate::s9pk::manifest::Manifest; use crate::s9pk::manifest::Manifest;
use crate::util::docker::stop_container; use crate::util::docker::stop_container;
use crate::Error; use crate::Error;
@@ -16,11 +18,13 @@ impl ManagerSeed {
pub async fn stop_container(&self) -> Result<(), Error> { pub async fn stop_container(&self) -> Result<(), Error> {
match stop_container( match stop_container(
&self.container_name, &self.container_name,
self.manifest match &self.manifest.main {
.containers PackageProcedure::Docker(DockerProcedure {
.as_ref() sigterm_timeout: Some(sigterm_timeout),
.and_then(|c| c.main.sigterm_timeout) ..
.map(|d| *d), }) => Some(**sigterm_timeout),
_ => None,
},
None, None,
) )
.await .await

View File

@@ -13,7 +13,8 @@ pub fn pbkdf2(password: impl AsRef<[u8]>, salt: impl AsRef<[u8]>) -> CipherKey<A
salt.as_ref(), salt.as_ref(),
1000, 1000,
aeskey.as_mut_slice(), aeskey.as_mut_slice(),
).unwrap(); )
.unwrap();
aeskey aeskey
} }

View File

@@ -5,6 +5,7 @@ use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use futures::FutureExt; use futures::FutureExt;
use libc::time_t;
use openssl::asn1::{Asn1Integer, Asn1Time}; use openssl::asn1::{Asn1Integer, Asn1Time};
use openssl::bn::{BigNum, MsbOption}; use openssl::bn::{BigNum, MsbOption};
use openssl::ec::{EcGroup, EcKey}; use openssl::ec::{EcGroup, EcKey};
@@ -20,12 +21,20 @@ use tracing::instrument;
use crate::account::AccountInfo; use crate::account::AccountInfo;
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::hostname::Hostname; use crate::hostname::Hostname;
use crate::init::check_time_is_synchronized;
use crate::net::dhcp::ips; use crate::net::dhcp::ips;
use crate::net::keys::{Key, KeyInfo}; use crate::net::keys::{Key, KeyInfo};
use crate::{Error, ErrorKind, ResultExt}; use crate::{Error, ErrorKind, ResultExt, SOURCE_DATE};
static CERTIFICATE_VERSION: i32 = 2; // X509 version 3 is actually encoded as '2' in the cert because fuck you. static CERTIFICATE_VERSION: i32 = 2; // X509 version 3 is actually encoded as '2' in the cert because fuck you.
fn unix_time(time: SystemTime) -> time_t {
time.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as time_t)
.or_else(|_| UNIX_EPOCH.elapsed().map(|d| -(d.as_secs() as time_t)))
.unwrap_or_default()
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct CertPair { pub struct CertPair {
pub ed25519: X509, pub ed25519: X509,
@@ -55,9 +64,13 @@ impl CertPair {
}), }),
); );
if cert if cert
.not_after() .not_before()
.compare(Asn1Time::days_from_now(30)?.as_ref())? .compare(Asn1Time::days_from_now(0)?.as_ref())?
== Ordering::Greater == Ordering::Less
&& cert
.not_after()
.compare(Asn1Time::days_from_now(30)?.as_ref())?
== Ordering::Greater
&& ips.is_superset(&ip) && ips.is_superset(&ip)
{ {
return Ok(cert.clone()); return Ok(cert.clone());
@@ -80,6 +93,14 @@ impl CertPair {
} }
} }
pub async fn root_ca_start_time() -> Result<SystemTime, Error> {
Ok(if check_time_is_synchronized().await? {
SystemTime::now()
} else {
*SOURCE_DATE
})
}
#[derive(Debug)] #[derive(Debug)]
pub struct SslManager { pub struct SslManager {
hostname: Hostname, hostname: Hostname,
@@ -89,9 +110,13 @@ pub struct SslManager {
cert_cache: RwLock<BTreeMap<Key, CertPair>>, cert_cache: RwLock<BTreeMap<Key, CertPair>>,
} }
impl SslManager { impl SslManager {
pub fn new(account: &AccountInfo) -> Result<Self, Error> { pub fn new(account: &AccountInfo, start_time: SystemTime) -> Result<Self, Error> {
let int_key = generate_key()?; let int_key = generate_key()?;
let int_cert = make_int_cert((&account.root_ca_key, &account.root_ca_cert), &int_key)?; let int_cert = make_int_cert(
(&account.root_ca_key, &account.root_ca_cert),
&int_key,
start_time,
)?;
Ok(Self { Ok(Self {
hostname: account.hostname.clone(), hostname: account.hostname.clone(),
root_cert: account.root_ca_cert.clone(), root_cert: account.root_ca_cert.clone(),
@@ -160,14 +185,20 @@ pub fn generate_key() -> Result<PKey<Private>, Error> {
} }
#[instrument(skip_all)] #[instrument(skip_all)]
pub fn make_root_cert(root_key: &PKey<Private>, hostname: &Hostname) -> Result<X509, Error> { pub fn make_root_cert(
root_key: &PKey<Private>,
hostname: &Hostname,
start_time: SystemTime,
) -> Result<X509, Error> {
let mut builder = X509Builder::new()?; let mut builder = X509Builder::new()?;
builder.set_version(CERTIFICATE_VERSION)?; builder.set_version(CERTIFICATE_VERSION)?;
let embargo = Asn1Time::days_from_now(0)?; let unix_start_time = unix_time(start_time);
let embargo = Asn1Time::from_unix(unix_start_time - 86400)?;
builder.set_not_before(&embargo)?; builder.set_not_before(&embargo)?;
let expiration = Asn1Time::days_from_now(3650)?; let expiration = Asn1Time::from_unix(unix_start_time + (10 * 364 * 86400))?;
builder.set_not_after(&expiration)?; builder.set_not_after(&expiration)?;
builder.set_serial_number(&*rand_serial()?)?; builder.set_serial_number(&*rand_serial()?)?;
@@ -214,14 +245,17 @@ pub fn make_root_cert(root_key: &PKey<Private>, hostname: &Hostname) -> Result<X
pub fn make_int_cert( pub fn make_int_cert(
signer: (&PKey<Private>, &X509), signer: (&PKey<Private>, &X509),
applicant: &PKey<Private>, applicant: &PKey<Private>,
start_time: SystemTime,
) -> Result<X509, Error> { ) -> Result<X509, Error> {
let mut builder = X509Builder::new()?; let mut builder = X509Builder::new()?;
builder.set_version(CERTIFICATE_VERSION)?; builder.set_version(CERTIFICATE_VERSION)?;
let embargo = Asn1Time::days_from_now(0)?; let unix_start_time = unix_time(start_time);
let embargo = Asn1Time::from_unix(unix_start_time - 86400)?;
builder.set_not_before(&embargo)?; builder.set_not_before(&embargo)?;
let expiration = Asn1Time::days_from_now(3650)?; let expiration = Asn1Time::from_unix(unix_start_time + (10 * 364 * 86400))?;
builder.set_not_after(&expiration)?; builder.set_not_after(&expiration)?;
builder.set_serial_number(&*rand_serial()?)?; builder.set_serial_number(&*rand_serial()?)?;
@@ -344,17 +378,10 @@ pub fn make_leaf_cert(
let mut builder = X509Builder::new()?; let mut builder = X509Builder::new()?;
builder.set_version(CERTIFICATE_VERSION)?; builder.set_version(CERTIFICATE_VERSION)?;
let embargo = Asn1Time::from_unix( let embargo = Asn1Time::from_unix(unix_time(SystemTime::now()) - 86400)?;
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.or_else(|_| UNIX_EPOCH.elapsed().map(|d| -(d.as_secs() as i64)))
.unwrap_or_default()
- 86400,
)?;
builder.set_not_before(&embargo)?; builder.set_not_before(&embargo)?;
// Google Apple and Mozilla reject certificate horizons longer than 397 days // Google Apple and Mozilla reject certificate horizons longer than 398 days
// https://techbeacon.com/security/google-apple-mozilla-enforce-1-year-max-security-certifications // https://techbeacon.com/security/google-apple-mozilla-enforce-1-year-max-security-certifications
let expiration = Asn1Time::days_from_now(397)?; let expiration = Asn1Time::days_from_now(397)?;
builder.set_not_after(&expiration)?; builder.set_not_after(&expiration)?;

View File

@@ -272,7 +272,14 @@ impl VHostServer {
.await .await
.with_kind(crate::ErrorKind::OpenSsl)?; .with_kind(crate::ErrorKind::OpenSsl)?;
let mut tls_stream = let mut tls_stream =
mid.into_stream(Arc::new(cfg)).await?; match mid.into_stream(Arc::new(cfg)).await {
Ok(a) => a,
Err(e) => {
tracing::trace!( "VHostController: failed to accept TLS connection on port {port}: {e}");
tracing::trace!("{e:?}");
return Ok(())
}
};
tls_stream.get_mut().0.stop_buffering(); tls_stream.get_mut().0.stop_buffering();
tokio::io::copy_bidirectional( tokio::io::copy_bidirectional(
&mut tls_stream, &mut tls_stream,
@@ -287,7 +294,14 @@ impl VHostServer {
cfg.alpn_protocols.push(proto.into()); cfg.alpn_protocols.push(proto.into());
} }
let mut tls_stream = let mut tls_stream =
mid.into_stream(Arc::new(cfg)).await?; match mid.into_stream(Arc::new(cfg)).await {
Ok(a) => a,
Err(e) => {
tracing::trace!( "VHostController: failed to accept TLS connection on port {port}: {e}");
tracing::trace!("{e:?}");
return Ok(())
}
};
tls_stream.get_mut().0.stop_buffering(); tls_stream.get_mut().0.stop_buffering();
tokio::io::copy_bidirectional( tokio::io::copy_bidirectional(
&mut tls_stream, &mut tls_stream,
@@ -298,7 +312,14 @@ impl VHostServer {
Err(AlpnInfo::Specified(alpn)) => { Err(AlpnInfo::Specified(alpn)) => {
cfg.alpn_protocols = alpn; cfg.alpn_protocols = alpn;
let mut tls_stream = let mut tls_stream =
mid.into_stream(Arc::new(cfg)).await?; match mid.into_stream(Arc::new(cfg)).await {
Ok(a) => a,
Err(e) => {
tracing::trace!( "VHostController: failed to accept TLS connection on port {port}: {e}");
tracing::trace!("{e:?}");
return Ok(())
}
};
tls_stream.get_mut().0.stop_buffering(); tls_stream.get_mut().0.stop_buffering();
tokio::io::copy_bidirectional( tokio::io::copy_bidirectional(
&mut tls_stream, &mut tls_stream,
@@ -308,10 +329,12 @@ impl VHostServer {
} }
} }
.map_or_else( .map_or_else(
|e| match e.kind() { |e| {
std::io::ErrorKind::UnexpectedEof => Ok(()), use std::io::ErrorKind as E;
match e.kind() {
E::UnexpectedEof | E::BrokenPipe | E::ConnectionAborted | E::ConnectionReset | E::ConnectionRefused | E::TimedOut | E::Interrupted | E::NotConnected => Ok(()),
_ => Err(e), _ => Err(e),
}, }},
|_| Ok(()), |_| Ok(()),
)?; )?;
} else { } else {
@@ -327,8 +350,10 @@ impl VHostServer {
}); });
} }
Err(e) => { Err(e) => {
tracing::error!("Error in VHostController on port {port}: {e}"); tracing::trace!(
tracing::debug!("{e:?}"); "VHostController: failed to accept connection on port {port}: {e}"
);
tracing::trace!("{e:?}");
} }
} }
} }

View File

@@ -6,9 +6,7 @@ use std::os::unix::prelude::FileTypeExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;
use async_stream::stream;
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use color_eyre::Report;
use futures::future::{BoxFuture, Either as EitherFuture}; use futures::future::{BoxFuture, Either as EitherFuture};
use futures::{FutureExt, TryStreamExt}; use futures::{FutureExt, TryStreamExt};
use helpers::{NonDetachingJoinHandle, UnixRpcClient}; use helpers::{NonDetachingJoinHandle, UnixRpcClient};
@@ -396,7 +394,7 @@ impl DockerProcedure {
cmd.arg("exec"); cmd.arg("exec");
cmd.args(self.docker_args_inject(pkg_id).await?); cmd.args(self.docker_args_inject(pkg_id));
let input_buf = if let (Some(input), Some(format)) = (&input, &self.io_format) { let input_buf = if let (Some(input), Some(format)) = (&input, &self.io_format) {
cmd.stdin(std::process::Stdio::piped()); cmd.stdin(std::process::Stdio::piped());
Some(format.to_vec(input)?) Some(format.to_vec(input)?)
@@ -756,7 +754,7 @@ impl DockerProcedure {
+ self.args.len(), // [ARG...] + self.args.len(), // [ARG...]
) )
} }
async fn docker_args_inject(&self, pkg_id: &PackageId) -> Result<Vec<Cow<'_, OsStr>>, Error> { fn docker_args_inject(&self, pkg_id: &PackageId) -> Vec<Cow<'_, OsStr>> {
let mut res = self.new_docker_args(); let mut res = self.new_docker_args();
if let Some(shm_size_mb) = self.shm_size_mb { if let Some(shm_size_mb) = self.shm_size_mb {
res.push(OsStr::new("--shm-size").into()); res.push(OsStr::new("--shm-size").into());
@@ -769,7 +767,7 @@ impl DockerProcedure {
res.extend(self.args.iter().map(|s| OsStr::new(s).into())); res.extend(self.args.iter().map(|s| OsStr::new(s).into()));
Ok(res) res
} }
} }
@@ -813,7 +811,7 @@ impl LongRunning {
socket_path: &Path, socket_path: &Path,
) -> Result<tokio::process::Command, Error> { ) -> Result<tokio::process::Command, Error> {
const INIT_EXEC: &str = "/start9/bin/embassy_container_init"; const INIT_EXEC: &str = "/start9/bin/embassy_container_init";
const BIND_LOCATION: &str = "/usr/lib/embassy/container/"; const BIND_LOCATION: &str = "/usr/lib/startos/container/";
tracing::trace!("setup_long_running_docker_cmd"); tracing::trace!("setup_long_running_docker_cmd");
remove_container(container_name, true).await?; remove_container(container_name, true).await?;
@@ -892,23 +890,12 @@ async fn buf_reader_to_lines(
reader: impl AsyncBufRead + Unpin, reader: impl AsyncBufRead + Unpin,
limit: impl Into<Option<usize>>, limit: impl Into<Option<usize>>,
) -> Result<Vec<String>, Error> { ) -> Result<Vec<String>, Error> {
let lines = stream! { let mut lines = reader.lines();
let mut lines = reader.lines(); let mut answer = RingVec::new(limit.into().unwrap_or(1000));
while let Some(line) = lines.next_line().await? { while let Some(line) = lines.next_line().await? {
yield Ok::<_, Report>(line); answer.push(line);
} }
}; let output: Vec<String> = answer.value.into_iter().collect();
let output: RingVec<String> = lines
.try_fold(
RingVec::new(limit.into().unwrap_or(1000)),
|mut acc, line| async move {
acc.push(line);
Ok(acc)
},
)
.await
.with_kind(crate::ErrorKind::Unknown)?;
let output: Vec<String> = output.value.into_iter().collect();
Ok(output) Ok(output)
} }
@@ -973,4 +960,11 @@ mod tests {
assert_eq!(CAPACITY_IN, ring.value.capacity()); assert_eq!(CAPACITY_IN, ring.value.capacity());
assert_eq!(CAPACITY_IN, ring.value.len()); assert_eq!(CAPACITY_IN, ring.value.len());
} }
#[test]
fn tests_buf_reader_to_lines() {
let mut reader = BufReader::new("hello\nworld\n".as_bytes());
let lines = futures::executor::block_on(buf_reader_to_lines(&mut reader, None)).unwrap();
assert_eq!(lines, vec!["hello", "world"]);
}
} }

View File

@@ -1,11 +1,7 @@
use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::{
path::{Path, PathBuf},
process::Stdio,
};
use color_eyre::eyre::eyre;
use embassy_container_init::ProcessGroupId; use embassy_container_init::ProcessGroupId;
use helpers::UnixRpcClient; use helpers::UnixRpcClient;
pub use js_engine::JsError; pub use js_engine::JsError;
@@ -19,8 +15,8 @@ use tracing::instrument;
use super::ProcedureName; use super::ProcedureName;
use crate::prelude::*; use crate::prelude::*;
use crate::s9pk::manifest::PackageId; use crate::s9pk::manifest::PackageId;
use crate::util::io::to_json_async_writer; use crate::util::serde::IoFormat;
use crate::util::Version; use crate::util::{Invoke, Version};
use crate::volume::Volumes; use crate::volume::Volumes;
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@@ -85,45 +81,23 @@ impl JsProcedure {
_gid: ProcessGroupId, _gid: ProcessGroupId,
_rpc_client: Option<Arc<UnixRpcClient>>, _rpc_client: Option<Arc<UnixRpcClient>>,
) -> Result<Result<O, (i32, String)>, Error> { ) -> Result<Result<O, (i32, String)>, Error> {
let runner_argument = ExecuteArgs { Command::new("start-deno")
procedure: self.clone(),
directory: directory.clone(),
pkg_id: pkg_id.clone(),
pkg_version: pkg_version.clone(),
name,
volumes: volumes.clone(),
input: input.and_then(|x| serde_json::to_value(x).ok()),
};
let mut runner = Command::new("start-deno")
.arg("execute") .arg("execute")
.stdin(Stdio::piped()) .input(Some(&mut std::io::Cursor::new(IoFormat::Json.to_vec(
.stdout(Stdio::piped()) &ExecuteArgs {
.stderr(Stdio::piped()) procedure: self.clone(),
.kill_on_drop(true) directory: directory.clone(),
.spawn()?; pkg_id: pkg_id.clone(),
to_json_async_writer( pkg_version: pkg_version.clone(),
&mut runner.stdin.take().or_not_found("stdin")?, name,
&runner_argument, volumes: volumes.clone(),
) input: input.and_then(|x| serde_json::to_value(x).ok()),
.await?; },
)?)))
let res = if let Some(timeout) = timeout { .timeout(timeout)
tokio::time::timeout(timeout, runner.wait_with_output()) .invoke(ErrorKind::Javascript)
.await .await
.with_kind(ErrorKind::Timeout)?? .and_then(|res| IoFormat::Json.from_slice(&res))
} else {
runner.wait_with_output().await?
};
if res.status.success() {
serde_json::from_str::<Result<O, (i32, String)>>(std::str::from_utf8(&res.stdout)?)
.with_kind(ErrorKind::Deserialization)
} else {
Err(Error::new(
eyre!("{}", String::from_utf8(res.stderr)?),
ErrorKind::Javascript,
))
}
} }
#[instrument(skip_all)] #[instrument(skip_all)]
@@ -137,45 +111,23 @@ impl JsProcedure {
timeout: Option<Duration>, timeout: Option<Duration>,
name: ProcedureName, name: ProcedureName,
) -> Result<Result<O, (i32, String)>, Error> { ) -> Result<Result<O, (i32, String)>, Error> {
let runner_argument = ExecuteArgs { Command::new("start-deno")
procedure: self.clone(),
directory: directory.clone(),
pkg_id: pkg_id.clone(),
pkg_version: pkg_version.clone(),
name,
volumes: volumes.clone(),
input: input.and_then(|x| serde_json::to_value(x).ok()),
};
let mut runner = Command::new("start-deno")
.arg("sandbox") .arg("sandbox")
.stdin(Stdio::piped()) .input(Some(&mut std::io::Cursor::new(IoFormat::Json.to_vec(
.stdout(Stdio::piped()) &ExecuteArgs {
.stderr(Stdio::piped()) procedure: self.clone(),
.kill_on_drop(true) directory: directory.clone(),
.spawn()?; pkg_id: pkg_id.clone(),
to_json_async_writer( pkg_version: pkg_version.clone(),
&mut runner.stdin.take().or_not_found("stdin")?, name,
&runner_argument, volumes: volumes.clone(),
) input: input.and_then(|x| serde_json::to_value(x).ok()),
.await?; },
)?)))
let res = if let Some(timeout) = timeout { .timeout(timeout)
tokio::time::timeout(timeout, runner.wait_with_output()) .invoke(ErrorKind::Javascript)
.await .await
.with_kind(ErrorKind::Timeout)?? .and_then(|res| IoFormat::Json.from_slice(&res))
} else {
runner.wait_with_output().await?
};
if res.status.success() {
serde_json::from_str::<Result<O, (i32, String)>>(std::str::from_utf8(&res.stdout)?)
.with_kind(ErrorKind::Deserialization)
} else {
Err(Error::new(
eyre!("{}", String::from_utf8(res.stderr)?),
ErrorKind::Javascript,
))
}
} }
#[instrument(skip_all)] #[instrument(skip_all)]

View File

@@ -172,7 +172,13 @@ impl<'de> Deserialize<'de> for NoOutput {
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let _ = Value::deserialize(deserializer)?; let _ = Value::deserialize(deserializer);
Ok(NoOutput) Ok(NoOutput)
} }
} }
#[test]
fn test_deser_no_output() {
serde_json::from_str::<NoOutput>("").unwrap();
serde_json::from_str::<Result<NoOutput, NoOutput>>("{\"Ok\": null}").unwrap();
}

View File

@@ -23,7 +23,7 @@ pub fn with_query_params(ctx: RpcContext, mut url: Url) -> Url {
"os.compat", "os.compat",
&crate::version::Current::new().compat().to_string(), &crate::version::Current::new().compat().to_string(),
) )
.append_pair("os.arch", crate::OS_ARCH) .append_pair("os.arch", &*crate::PLATFORM)
.append_pair("hardware.arch", &*crate::ARCH) .append_pair("hardware.arch", &*crate::ARCH)
.append_pair("hardware.ram", &ctx.hardware.ram.to_string()); .append_pair("hardware.ram", &ctx.hardware.ram.to_string());

View File

@@ -31,6 +31,7 @@ use crate::disk::REPAIR_DISK_PATH;
use crate::hostname::Hostname; use crate::hostname::Hostname;
use crate::init::{init, InitResult}; use crate::init::{init, InitResult};
use crate::middleware::encrypt::EncryptedWire; use crate::middleware::encrypt::EncryptedWire;
use crate::net::ssl::root_ca_start_time;
use crate::prelude::*; use crate::prelude::*;
use crate::util::io::{dir_copy, dir_size, Counter}; use crate::util::io::{dir_copy, dir_size, Counter};
use crate::{Error, ErrorKind, ResultExt}; use crate::{Error, ErrorKind, ResultExt};
@@ -378,7 +379,7 @@ async fn fresh_setup(
ctx: &SetupContext, ctx: &SetupContext,
embassy_password: &str, embassy_password: &str,
) -> Result<(Hostname, OnionAddressV3, X509), Error> { ) -> Result<(Hostname, OnionAddressV3, X509), Error> {
let account = AccountInfo::new(embassy_password)?; let account = AccountInfo::new(embassy_password, root_ca_start_time().await?)?;
let sqlite_pool = ctx.secret_store().await?; let sqlite_pool = ctx.secret_store().await?;
account.save(&sqlite_pool).await?; account.save(&sqlite_pool).await?;
sqlite_pool.close().await; sqlite_pool.close().await;

View File

@@ -6,10 +6,11 @@ use rpc_toolkit::command;
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::disk::main::export; use crate::disk::main::export;
use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH}; use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH};
use crate::prelude::*;
use crate::sound::SHUTDOWN; use crate::sound::SHUTDOWN;
use crate::util::docker::CONTAINER_TOOL; use crate::util::docker::CONTAINER_TOOL;
use crate::util::{display_none, Invoke}; use crate::util::{display_none, Invoke};
use crate::{Error, OS_ARCH}; use crate::PLATFORM;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Shutdown { pub struct Shutdown {
@@ -60,7 +61,7 @@ impl Shutdown {
tracing::debug!("{:?}", e); tracing::debug!("{:?}", e);
} }
} }
if OS_ARCH != "raspberrypi" || self.restart { if &*PLATFORM != "raspberrypi" || self.restart {
if let Err(e) = SHUTDOWN.play().await { if let Err(e) = SHUTDOWN.play().await {
tracing::error!("Error Playing Shutdown Song: {}", e); tracing::error!("Error Playing Shutdown Song: {}", e);
tracing::debug!("{:?}", e); tracing::debug!("{:?}", e);
@@ -68,7 +69,7 @@ impl Shutdown {
} }
}); });
drop(rt); drop(rt);
if OS_ARCH == "raspberrypi" { if &*PLATFORM == "raspberrypi" {
if !self.restart { if !self.restart {
std::fs::write(STANDBY_MODE_PATH, "").unwrap(); std::fs::write(STANDBY_MODE_PATH, "").unwrap();
Command::new("sync").spawn().unwrap().wait().unwrap(); Command::new("sync").spawn().unwrap().wait().unwrap();
@@ -90,6 +91,14 @@ impl Shutdown {
#[command(display(display_none))] #[command(display(display_none))]
pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> { pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> {
ctx.db
.mutate(|db| {
db.as_server_info_mut()
.as_status_info_mut()
.as_shutting_down_mut()
.ser(&true)
})
.await?;
ctx.shutdown ctx.shutdown
.send(Some(Shutdown { .send(Some(Shutdown {
export_args: Some((ctx.disk_guid.clone(), ctx.datadir.clone())), export_args: Some((ctx.disk_guid.clone(), ctx.datadir.clone())),
@@ -102,6 +111,14 @@ pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> {
#[command(display(display_none))] #[command(display(display_none))]
pub async fn restart(#[context] ctx: RpcContext) -> Result<(), Error> { pub async fn restart(#[context] ctx: RpcContext) -> Result<(), Error> {
ctx.db
.mutate(|db| {
db.as_server_info_mut()
.as_status_info_mut()
.as_restarting_mut()
.ser(&true)
})
.await?;
ctx.shutdown ctx.shutdown
.send(Some(Shutdown { .send(Some(Shutdown {
export_args: Some((ctx.disk_guid.clone(), ctx.datadir.clone())), export_args: Some((ctx.disk_guid.clone(), ctx.datadir.clone())),

View File

@@ -1,6 +1,7 @@
use std::fmt; use std::fmt;
use chrono::Utc; use chrono::Utc;
use clap::ArgMatches;
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use futures::FutureExt; use futures::FutureExt;
use rpc_toolkit::command; use rpc_toolkit::command;
@@ -84,9 +85,65 @@ pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(),
Ok(()) Ok(())
} }
#[command] #[derive(Serialize, Deserialize)]
pub async fn time() -> Result<String, Error> { pub struct TimeInfo {
Ok(Utc::now().to_rfc3339()) now: String,
uptime: u64,
}
fn display_time(arg: TimeInfo, matches: &ArgMatches) {
use std::fmt::Write;
use prettytable::*;
if matches.is_present("format") {
return display_serializable(arg, matches);
}
let days = arg.uptime / (24 * 60 * 60);
let days_s = arg.uptime % (24 * 60 * 60);
let hours = days_s / (60 * 60);
let hours_s = arg.uptime % (60 * 60);
let minutes = hours_s / 60;
let seconds = arg.uptime % 60;
let mut uptime_string = String::new();
if days > 0 {
write!(&mut uptime_string, "{days} days").unwrap();
}
if hours > 0 {
if !uptime_string.is_empty() {
uptime_string += ", ";
}
write!(&mut uptime_string, "{hours} hours").unwrap();
}
if minutes > 0 {
if !uptime_string.is_empty() {
uptime_string += ", ";
}
write!(&mut uptime_string, "{minutes} minutes").unwrap();
}
if !uptime_string.is_empty() {
uptime_string += ", ";
}
write!(&mut uptime_string, "{seconds} seconds").unwrap();
let mut table = Table::new();
table.add_row(row![bc -> "NOW", &arg.now]);
table.add_row(row![bc -> "UPTIME", &uptime_string]);
table.print_tty(false).unwrap();
}
#[command(display(display_time))]
pub async fn time(
#[context] ctx: RpcContext,
#[allow(unused_variables)]
#[arg(long = "format")]
format: Option<IoFormat>,
) -> Result<TimeInfo, Error> {
Ok(TimeInfo {
now: Utc::now().to_rfc3339(),
uptime: ctx.start_time.elapsed().as_secs(),
})
} }
#[command( #[command(
@@ -303,60 +360,44 @@ impl<'de> Deserialize<'de> for GigaBytes {
} }
#[derive(Deserialize, Serialize, Clone, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsGeneral { pub struct MetricsGeneral {
#[serde(rename = "Temperature")] pub temperature: Option<Celsius>,
temperature: Option<Celsius>,
} }
#[derive(Deserialize, Serialize, Clone, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsMemory { pub struct MetricsMemory {
#[serde(rename = "Percentage Used")]
pub percentage_used: Percentage, pub percentage_used: Percentage,
#[serde(rename = "Total")]
pub total: MebiBytes, pub total: MebiBytes,
#[serde(rename = "Available")]
pub available: MebiBytes, pub available: MebiBytes,
#[serde(rename = "Used")]
pub used: MebiBytes, pub used: MebiBytes,
#[serde(rename = "Swap Total")] pub zram_total: MebiBytes,
pub swap_total: MebiBytes, pub zram_available: MebiBytes,
#[serde(rename = "Swap Free")] pub zram_used: MebiBytes,
pub swap_free: MebiBytes,
#[serde(rename = "Swap Used")]
pub swap_used: MebiBytes,
} }
#[derive(Deserialize, Serialize, Clone, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsCpu { pub struct MetricsCpu {
#[serde(rename = "User Space")] percentage_used: Percentage,
user_space: Percentage,
#[serde(rename = "Kernel Space")]
kernel_space: Percentage,
#[serde(rename = "I/O Wait")]
wait: Percentage,
#[serde(rename = "Idle")]
idle: Percentage, idle: Percentage,
#[serde(rename = "Usage")] user_space: Percentage,
usage: Percentage, kernel_space: Percentage,
wait: Percentage,
} }
#[derive(Deserialize, Serialize, Clone, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsDisk { pub struct MetricsDisk {
#[serde(rename = "Size")] percentage_used: Percentage,
size: GigaBytes,
#[serde(rename = "Used")]
used: GigaBytes, used: GigaBytes,
#[serde(rename = "Available")]
available: GigaBytes, available: GigaBytes,
#[serde(rename = "Percentage Used")] capacity: GigaBytes,
used_percentage: Percentage,
} }
#[derive(Deserialize, Serialize, Clone, Debug)] #[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Metrics { pub struct Metrics {
#[serde(rename = "General")]
general: MetricsGeneral, general: MetricsGeneral,
#[serde(rename = "Memory")]
memory: MetricsMemory, memory: MetricsMemory,
#[serde(rename = "CPU")]
cpu: MetricsCpu, cpu: MetricsCpu,
#[serde(rename = "Disk")]
disk: MetricsDisk, disk: MetricsDisk,
} }
@@ -682,7 +723,7 @@ async fn get_cpu_info(last: &mut ProcStat) -> Result<MetricsCpu, Error> {
kernel_space: Percentage((new.system() - last.system()) as f64 * 100.0 / total_diff as f64), kernel_space: Percentage((new.system() - last.system()) as f64 * 100.0 / total_diff as f64),
idle: Percentage((new.idle - last.idle) as f64 * 100.0 / total_diff as f64), idle: Percentage((new.idle - last.idle) as f64 * 100.0 / total_diff as f64),
wait: Percentage((new.iowait - last.iowait) as f64 * 100.0 / total_diff as f64), wait: Percentage((new.iowait - last.iowait) as f64 * 100.0 / total_diff as f64),
usage: Percentage((new.used() - last.used()) as f64 * 100.0 / total_diff as f64), percentage_used: Percentage((new.used() - last.used()) as f64 * 100.0 / total_diff as f64),
}; };
*last = new; *last = new;
Ok(res) Ok(res)
@@ -695,8 +736,8 @@ pub struct MemInfo {
buffers: Option<u64>, buffers: Option<u64>,
cached: Option<u64>, cached: Option<u64>,
slab: Option<u64>, slab: Option<u64>,
swap_total: Option<u64>, zram_total: Option<u64>,
swap_free: Option<u64>, zram_free: Option<u64>,
} }
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn get_mem_info() -> Result<MetricsMemory, Error> { pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
@@ -708,8 +749,8 @@ pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
buffers: None, buffers: None,
cached: None, cached: None,
slab: None, slab: None,
swap_total: None, zram_total: None,
swap_free: None, zram_free: None,
}; };
fn get_num_kb(l: &str) -> Result<u64, Error> { fn get_num_kb(l: &str) -> Result<u64, Error> {
let e = Error::new( let e = Error::new(
@@ -734,8 +775,8 @@ pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
_ if entry.starts_with("Buffers") => mem_info.buffers = Some(get_num_kb(entry)?), _ if entry.starts_with("Buffers") => mem_info.buffers = Some(get_num_kb(entry)?),
_ if entry.starts_with("Cached") => mem_info.cached = Some(get_num_kb(entry)?), _ if entry.starts_with("Cached") => mem_info.cached = Some(get_num_kb(entry)?),
_ if entry.starts_with("Slab") => mem_info.slab = Some(get_num_kb(entry)?), _ if entry.starts_with("Slab") => mem_info.slab = Some(get_num_kb(entry)?),
_ if entry.starts_with("SwapTotal") => mem_info.swap_total = Some(get_num_kb(entry)?), _ if entry.starts_with("SwapTotal") => mem_info.zram_total = Some(get_num_kb(entry)?),
_ if entry.starts_with("SwapFree") => mem_info.swap_free = Some(get_num_kb(entry)?), _ if entry.starts_with("SwapFree") => mem_info.zram_free = Some(get_num_kb(entry)?),
_ => (), _ => (),
} }
} }
@@ -751,24 +792,24 @@ pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
let buffers = ensure_present(mem_info.buffers, "Buffers")?; let buffers = ensure_present(mem_info.buffers, "Buffers")?;
let cached = ensure_present(mem_info.cached, "Cached")?; let cached = ensure_present(mem_info.cached, "Cached")?;
let slab = ensure_present(mem_info.slab, "Slab")?; let slab = ensure_present(mem_info.slab, "Slab")?;
let swap_total_k = ensure_present(mem_info.swap_total, "SwapTotal")?; let zram_total_k = ensure_present(mem_info.zram_total, "SwapTotal")?;
let swap_free_k = ensure_present(mem_info.swap_free, "SwapFree")?; let zram_free_k = ensure_present(mem_info.zram_free, "SwapFree")?;
let total = MebiBytes(mem_total as f64 / 1024.0); let total = MebiBytes(mem_total as f64 / 1024.0);
let available = MebiBytes(mem_available as f64 / 1024.0); let available = MebiBytes(mem_available as f64 / 1024.0);
let used = MebiBytes((mem_total - mem_free - buffers - cached - slab) as f64 / 1024.0); let used = MebiBytes((mem_total - mem_free - buffers - cached - slab) as f64 / 1024.0);
let swap_total = MebiBytes(swap_total_k as f64 / 1024.0); let zram_total = MebiBytes(zram_total_k as f64 / 1024.0);
let swap_free = MebiBytes(swap_free_k as f64 / 1024.0); let zram_available = MebiBytes(zram_free_k as f64 / 1024.0);
let swap_used = MebiBytes((swap_total_k - swap_free_k) as f64 / 1024.0); let zram_used = MebiBytes((zram_total_k - zram_free_k) as f64 / 1024.0);
let percentage_used = Percentage((total.0 - available.0) / total.0 * 100.0); let percentage_used = Percentage((total.0 - available.0) / total.0 * 100.0);
Ok(MetricsMemory { Ok(MetricsMemory {
percentage_used, percentage_used,
total, total,
available, available,
used, used,
swap_total, zram_total,
swap_free, zram_available,
swap_used, zram_used,
}) })
} }
@@ -792,10 +833,10 @@ async fn get_disk_info() -> Result<MetricsDisk, Error> {
let total_percentage = total_used as f64 / total_size as f64 * 100.0f64; let total_percentage = total_used as f64 / total_size as f64 * 100.0f64;
Ok(MetricsDisk { Ok(MetricsDisk {
size: GigaBytes(total_size as f64 / 1_000_000_000.0), capacity: GigaBytes(total_size as f64 / 1_000_000_000.0),
used: GigaBytes(total_used as f64 / 1_000_000_000.0), used: GigaBytes(total_used as f64 / 1_000_000_000.0),
available: GigaBytes(total_available as f64 / 1_000_000_000.0), available: GigaBytes(total_available as f64 / 1_000_000_000.0),
used_percentage: Percentage(total_percentage as f64), percentage_used: Percentage(total_percentage as f64),
}) })
} }

View File

@@ -25,7 +25,7 @@ use crate::sound::{
}; };
use crate::update::latest_information::LatestInformation; use crate::update::latest_information::LatestInformation;
use crate::util::Invoke; use crate::util::Invoke;
use crate::{Error, ErrorKind, ResultExt, OS_ARCH}; use crate::{Error, ErrorKind, ResultExt, PLATFORM};
mod latest_information; mod latest_information;
@@ -231,7 +231,7 @@ impl EosUrl {
.host_str() .host_str()
.ok_or_else(|| Error::new(eyre!("Could not get host of base"), ErrorKind::ParseUrl))?; .ok_or_else(|| Error::new(eyre!("Could not get host of base"), ErrorKind::ParseUrl))?;
let version: &Version = &self.version; let version: &Version = &self.version;
Ok(format!("{host}::{version}/{OS_ARCH}/") Ok(format!("{host}::{version}/{}/", &*PLATFORM)
.parse() .parse()
.map_err(|_| Error::new(eyre!("Could not parse path"), ErrorKind::ParseUrl))?) .map_err(|_| Error::new(eyre!("Could not parse path"), ErrorKind::ParseUrl))?)
} }
@@ -297,7 +297,7 @@ async fn sync_boot() -> Result<(), Error> {
.await? .await?
.wait() .wait()
.await?; .await?;
if OS_ARCH != "raspberrypi" { if &*PLATFORM != "raspberrypi" {
let dev_mnt = let dev_mnt =
MountGuard::mount(&Bind::new("/dev"), "/media/embassy/next/dev", ReadWrite).await?; MountGuard::mount(&Bind::new("/dev"), "/media/embassy/next/dev", ReadWrite).await?;
let sys_mnt = let sys_mnt =

View File

@@ -1,13 +1,9 @@
use ed25519_dalek::hazmat::ExpandedSecretKey;
use ed25519_dalek::{SecretKey, EXPANDED_SECRET_KEY_LENGTH}; use ed25519_dalek::{SecretKey, EXPANDED_SECRET_KEY_LENGTH};
#[inline] #[inline]
pub fn ed25519_expand_key(key: &SecretKey) -> [u8; EXPANDED_SECRET_KEY_LENGTH] { pub fn ed25519_expand_key(key: &SecretKey) -> [u8; EXPANDED_SECRET_KEY_LENGTH] {
let key = ExpandedSecretKey::from(key); ed25519_dalek_v1::ExpandedSecretKey::from(
&ed25519_dalek_v1::SecretKey::from_bytes(key).unwrap(),
let mut bytes: [u8; 64] = [0u8; 64]; )
.to_bytes()
bytes[..32].copy_from_slice(key.scalar.as_bytes());
bytes[32..].copy_from_slice(&key.hash_prefix[..]);
bytes
} }

View File

@@ -7,14 +7,14 @@ use tokio::process::Command;
use crate::util::Invoke; use crate::util::Invoke;
#[cfg(not(feature = "podman"))] #[cfg(feature = "docker")]
pub const CONTAINER_TOOL: &str = "docker"; pub const CONTAINER_TOOL: &str = "docker";
#[cfg(feature = "podman")] #[cfg(not(feature = "docker"))]
pub const CONTAINER_TOOL: &str = "podman"; pub const CONTAINER_TOOL: &str = "podman";
#[cfg(not(feature = "podman"))] #[cfg(feature = "docker")]
pub const CONTAINER_DATADIR: &str = "/var/lib/docker"; pub const CONTAINER_DATADIR: &str = "/var/lib/docker";
#[cfg(feature = "podman")] #[cfg(not(feature = "docker"))]
pub const CONTAINER_DATADIR: &str = "/var/lib/containers"; pub const CONTAINER_DATADIR: &str = "/var/lib/containers";
pub struct DockerImageSha(String); pub struct DockerImageSha(String);

View File

@@ -26,13 +26,13 @@ use crate::shutdown::Shutdown;
use crate::{Error, ErrorKind, ResultExt as _}; use crate::{Error, ErrorKind, ResultExt as _};
pub mod config; pub mod config;
pub mod cpupower; pub mod cpupower;
pub mod crypto;
pub mod docker; pub mod docker;
pub mod http_reader; pub mod http_reader;
pub mod io; pub mod io;
pub mod logger; pub mod logger;
pub mod lshw; pub mod lshw;
pub mod serde; pub mod serde;
pub mod crypto;
#[derive(Clone, Copy, Debug, ::serde::Deserialize, ::serde::Serialize)] #[derive(Clone, Copy, Debug, ::serde::Deserialize, ::serde::Serialize)]
pub enum Never {} pub enum Never {}
@@ -50,30 +50,113 @@ impl std::fmt::Display for Never {
impl std::error::Error for Never {} impl std::error::Error for Never {}
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait Invoke { pub trait Invoke<'a> {
type Extended<'ext>
where
Self: 'ext,
'ext: 'a;
fn timeout<'ext: 'a>(&'ext mut self, timeout: Option<Duration>) -> Self::Extended<'ext>;
fn input<'ext: 'a, Input: tokio::io::AsyncRead + Unpin + Send>(
&'ext mut self,
input: Option<&'ext mut Input>,
) -> Self::Extended<'ext>;
async fn invoke(&mut self, error_kind: crate::ErrorKind) -> Result<Vec<u8>, Error>; async fn invoke(&mut self, error_kind: crate::ErrorKind) -> Result<Vec<u8>, Error>;
async fn invoke_timeout(
&mut self,
error_kind: crate::ErrorKind,
timeout: Option<Duration>,
) -> Result<Vec<u8>, Error>;
} }
#[async_trait::async_trait]
impl Invoke for tokio::process::Command { pub struct ExtendedCommand<'a> {
async fn invoke(&mut self, error_kind: crate::ErrorKind) -> Result<Vec<u8>, Error> { cmd: &'a mut tokio::process::Command,
self.invoke_timeout(error_kind, None).await timeout: Option<Duration>,
input: Option<&'a mut (dyn tokio::io::AsyncRead + Unpin + Send)>,
}
impl<'a> std::ops::Deref for ExtendedCommand<'a> {
type Target = tokio::process::Command;
fn deref(&self) -> &Self::Target {
&*self.cmd
} }
async fn invoke_timeout( }
&mut self, impl<'a> std::ops::DerefMut for ExtendedCommand<'a> {
error_kind: crate::ErrorKind, fn deref_mut(&mut self) -> &mut Self::Target {
timeout: Option<Duration>, self.cmd
) -> Result<Vec<u8>, Error> { }
self.kill_on_drop(true); }
self.stdout(Stdio::piped());
self.stderr(Stdio::piped()); #[async_trait::async_trait]
let res = match timeout { impl<'a> Invoke<'a> for tokio::process::Command {
None => self.output().await?, type Extended<'ext> = ExtendedCommand<'ext>
Some(t) => tokio::time::timeout(t, self.output()) where
Self: 'ext,
'ext: 'a;
fn timeout<'ext: 'a>(&'ext mut self, timeout: Option<Duration>) -> Self::Extended<'ext> {
ExtendedCommand {
cmd: self,
timeout,
input: None,
}
}
fn input<'ext: 'a, Input: tokio::io::AsyncRead + Unpin + Send>(
&'ext mut self,
input: Option<&'ext mut Input>,
) -> Self::Extended<'ext> {
ExtendedCommand {
cmd: self,
timeout: None,
input: if let Some(input) = input {
Some(&mut *input)
} else {
None
},
}
}
async fn invoke(&mut self, error_kind: crate::ErrorKind) -> Result<Vec<u8>, Error> {
ExtendedCommand {
cmd: self,
timeout: None,
input: None,
}
.invoke(error_kind)
.await
}
}
#[async_trait::async_trait]
impl<'a> Invoke<'a> for ExtendedCommand<'a> {
type Extended<'ext> = &'ext mut ExtendedCommand<'ext>
where
Self: 'ext,
'ext: 'a;
fn timeout<'ext: 'a>(&'ext mut self, timeout: Option<Duration>) -> Self::Extended<'ext> {
self.timeout = timeout;
self
}
fn input<'ext: 'a, Input: tokio::io::AsyncRead + Unpin + Send>(
&'ext mut self,
input: Option<&'ext mut Input>,
) -> Self::Extended<'ext> {
self.input = if let Some(input) = input {
Some(&mut *input)
} else {
None
};
self
}
async fn invoke(&mut self, error_kind: crate::ErrorKind) -> Result<Vec<u8>, Error> {
self.cmd.kill_on_drop(true);
if self.input.is_some() {
self.cmd.stdin(Stdio::piped());
}
self.cmd.stdout(Stdio::piped());
self.cmd.stderr(Stdio::piped());
let mut child = self.cmd.spawn()?;
if let (Some(mut stdin), Some(input)) = (child.stdin.take(), self.input.take()) {
use tokio::io::AsyncWriteExt;
tokio::io::copy(input, &mut stdin).await?;
stdin.flush().await?;
stdin.shutdown().await?;
drop(stdin);
}
let res = match self.timeout {
None => child.wait_with_output().await?,
Some(t) => tokio::time::timeout(t, child.wait_with_output())
.await .await
.with_kind(ErrorKind::Timeout)??, .with_kind(ErrorKind::Timeout)??,
}; };

View File

@@ -200,8 +200,7 @@ pub async fn init(db: &PatchDb, secrets: &PgPool) -> Result<(), Error> {
Ok(()) Ok(())
} }
pub const COMMIT_HASH: &str = pub const COMMIT_HASH: &str = include_str!("../../../GIT_HASH.txt");
git_version::git_version!(args = ["--always", "--abbrev=40", "--dirty=-modified"]);
#[command(rename = "git-info", local, metadata(authenticated = false))] #[command(rename = "git-info", local, metadata(authenticated = false))]
pub fn git_info() -> Result<&'static str, Error> { pub fn git_info() -> Result<&'static str, Error> {

View File

@@ -27,6 +27,12 @@ impl Volumes {
volume volume
.validate(interfaces) .validate(interfaces)
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, format!("Volume {}", id)))?; .with_ctx(|_| (crate::ErrorKind::ValidateS9pk, format!("Volume {}", id)))?;
if let Volume::Backup { .. } = volume {
return Err(Error::new(
eyre!("Invalid volume type \"backup\""),
ErrorKind::ParseS9pk,
)); // Volume::Backup is for internal use and shouldn't be declared in manifest
}
} }
Ok(()) Ok(())
} }
@@ -131,7 +137,6 @@ pub enum Volume {
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
Certificate { interface_id: InterfaceId }, Certificate { interface_id: InterfaceId },
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
#[serde(skip)]
Backup { readonly: bool }, Backup { readonly: bool },
} }
impl Volume { impl Volume {

View File

@@ -855,7 +855,7 @@ export const action = {
}, },
/** /**
* Created this test because of issue * Created this test because of issue
* https://github.com/Start9Labs/embassy-os/issues/1737 * https://github.com/Start9Labs/start-os/issues/1737
* which that we couldn't create a dir that was deeply nested, and the parents where * which that we couldn't create a dir that was deeply nested, and the parents where
* not created yet. Found this out during the migrations, where the parent would die. * not created yet. Found this out during the migrations, where the parent would die.
* @param {*} effects * @param {*} effects
@@ -931,7 +931,7 @@ export const action = {
}, },
/** /**
* Created this test because of issue * Created this test because of issue
* https://github.com/Start9Labs/embassy-os/issues/2121 * https://github.com/Start9Labs/start-os/issues/2121
* That the empty in the create dies * That the empty in the create dies
* @param {*} effects * @param {*} effects
* @param {*} _input * @param {*} _input

View File

@@ -15,7 +15,7 @@ docker run -d --rm --name=tmp_postgres -e POSTGRES_PASSWORD=password -v $TMP_DIR
PG_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' tmp_postgres) PG_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' tmp_postgres)
DATABASE_URL=postgres://postgres:password@$PG_IP/postgres cargo sqlx migrate run DATABASE_URL=postgres://postgres:password@$PG_IP/postgres cargo sqlx migrate run
DATABASE_URL=postgres://postgres:password@$PG_IP/postgres OS_ARCH=$(uname -m) cargo sqlx prepare -- --lib --profile=test DATABASE_URL=postgres://postgres:password@$PG_IP/postgres PLATFORM=$(uname -m) cargo sqlx prepare -- --lib --profile=test
) )
docker stop tmp_postgres docker stop tmp_postgres

19
basename.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
cd "$(dirname "${BASH_SOURCE[0]}")"
PLATFORM="$(if [ -f ./PLATFORM.txt ]; then cat ./PLATFORM.txt; else echo unknown; fi)"
VERSION="$(cat ./VERSION.txt)"
GIT_HASH="$(cat ./GIT_HASH.txt)"
if [[ "$GIT_HASH" =~ ^@ ]]; then
GIT_HASH=unknown
else
GIT_HASH="$(echo -n "$GIT_HASH" | head -c 7)"
fi
STARTOS_ENV="$(cat ./ENVIRONMENT.txt)"
VERSION_FULL="${VERSION}-${GIT_HASH}"
if [ -n "$STARTOS_ENV" ]; then
VERSION_FULL="$VERSION_FULL~${STARTOS_ENV}"
fi
echo -n "startos-${VERSION_FULL}_${PLATFORM}"

View File

@@ -4,7 +4,7 @@ set -e
shopt -s expand_aliases shopt -s expand_aliases
if [ "$0" != "./build-cargo-dep.sh" ]; then if [ "$0" != "./build-cargo-dep.sh" ]; then
>&2 echo "Must be run from embassy-os directory" >&2 echo "Must be run from start-os directory"
exit 1 exit 1
fi fi

2
build/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
lib/depends
lib/conflicts

View File

@@ -1,5 +1,5 @@
openresolv
dhcpcd5 dhcpcd5
firewalld firewalld
nginx nginx
nginx-common nginx-common
openresolv

View File

@@ -6,21 +6,15 @@ bmon
btrfs-progs btrfs-progs
ca-certificates ca-certificates
cifs-utils cifs-utils
containerd.io
cryptsetup cryptsetup
curl curl
dmidecode dmidecode
docker-ce
docker-ce-cli
docker-compose-plugin
dosfstools dosfstools
e2fsprogs e2fsprogs
ecryptfs-utils ecryptfs-utils
exfatprogs exfatprogs
flashrom flashrom
gdb
grub-common grub-common
heaptrack
htop htop
httpdirfs httpdirfs
iotop iotop

View File

@@ -0,0 +1,5 @@
+ containerd.io
+ docker-ce
+ docker-ce-cli
+ docker-compose-plugin
- podman

43
build/dpkg-deps/generate.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
set -e
cd "$(dirname "${BASH_SOURCE[0]}")"
IFS="-" read -ra FEATURES <<< "$ENVIRONMENT"
feature_file_checker='
/^#/ { next }
/^\+ [a-z0-9]+$/ { next }
/^- [a-z0-9]+$/ { next }
{ exit 1 }
'
for type in conflicts depends; do
pkgs=()
for feature in ${FEATURES[@]}; do
file="$feature.$type"
if [ -f $file ]; then
# TODO check for syntax errrors
cat $file | awk "$feature_file_checker"
for pkg in $(cat $file | awk '/^\+/ {print $2}'); do
pkgs+=($pkg)
done
fi
done
for pkg in $(cat $type); do
SKIP=
for feature in ${FEATURES[@]}; do
file="$feature.$type"
if [ -f $file ]; then
if grep "^- $pkg$" $file; then
SKIP=1
fi
fi
done
if [ -z $SKIP ]; then
pkgs+=($pkg)
fi
done
(IFS=$'\n'; echo "${pkgs[*]}") | sort -u > ../lib/$type
done

View File

@@ -0,0 +1,2 @@
+ gdb
+ heaptrack

View File

@@ -2,23 +2,33 @@
printf "\n" printf "\n"
printf "Welcome to\n" printf "Welcome to\n"
cat << "ASCII" cat << "ASCII"
╭ ━ ━ ━ ╮ ╭ ╮ ╭ ╮ ╭ ━ ━ ━ ┳ ━ ━ ━ ╮
┃ ╭ ━ ╮ ┣ ╯ ╰ ╮ ╭ ╯ ╰ ┫ ╭ ━ ╮ ┃ ╭ ━ ╮ ┃ ███████
┃ ╰ ━ ━ ╋ ╮ ╭ ╋ ━ ━ ┳ ┻ ╮ ╭ ┫ ┃ ┃ ┃ ╰ ━ ━ ╮ █ █ █
╰ ━ ━ ╮ ┃ ┃ ┃ ┃ ╭ ╮ ┃ ╭ ┫ ┃ ┃ ┃ ┃ ┣ ━ ━ ╮ ┃ █ █ █ █
┃ ╰ ━ ╯ ┃ ┃ ╰ ┫ ╭ ╮ ┃ ┃ ┃ ╰ ┫ ╰ ━ ╯ ┃ ╰ ━ ╯ ┃ █ █ █ █
╰ ━ ━ ━ ╯ ╰ ━ ┻ ╯ ╰ ┻ ╯ ╰ ━ ┻ ━ ━ ━ ┻ ━ ━ ━ ╯ █ █ █ █
█ █ █ █
█ █
███████
_____ __ ___ __ __
(_ | /\ |__) | / \(_
__) | / \| \ | \__/__)
ASCII ASCII
printf " v$(cat /usr/lib/startos/VERSION.txt)\n\n"
printf " %s (%s %s)\n" "$(uname -o)" "$(uname -r)" "$(uname -m)" printf " %s (%s %s)\n" "$(uname -o)" "$(uname -r)" "$(uname -m)"
printf " $(start-cli --version | sed 's/StartOS CLI /StartOS v/g') - $(start-cli git-info)" printf " Git Hash: $(cat /usr/lib/startos/GIT_HASH.txt)"
if [ -n "$(cat /usr/lib/embassy/ENVIRONMENT.txt)" ]; then if [ -n "$(cat /usr/lib/startos/ENVIRONMENT.txt)" ]; then
printf " ~ $(cat /usr/lib/embassy/ENVIRONMENT.txt)\n" printf " ~ $(cat /usr/lib/startos/ENVIRONMENT.txt)\n"
else else
printf "\n" printf "\n"
fi fi
printf "\n" printf "\n"
printf " * Documentation: https://start9.com\n" printf " * Documentation: https://docs.start9.com\n"
printf " * Management: https://%s.local\n" "$(hostname)" printf " * Management: https://%s.local\n" "$(hostname)"
printf " * Support: https://t.me/start9_labs\n" printf " * Support: https://start9.com/contact\n"
printf " * Source Code: https://github.com/Start9Labs/start-os\n"
printf " * License: MIT\n"
printf "\n" printf "\n"

View File

@@ -72,7 +72,7 @@ user_pref("messaging-system.rsexperimentloader.enabled", false);
user_pref("network.allow-experiments", false); user_pref("network.allow-experiments", false);
user_pref("network.captive-portal-service.enabled", false); user_pref("network.captive-portal-service.enabled", false);
user_pref("network.connectivity-service.enabled", false); user_pref("network.connectivity-service.enabled", false);
user_pref("network.proxy.autoconfig_url", "file:///usr/lib/embassy/proxy.pac"); user_pref("network.proxy.autoconfig_url", "file:///usr/lib/startos/proxy.pac");
user_pref("network.proxy.socks_remote_dns", true); user_pref("network.proxy.socks_remote_dns", true);
user_pref("network.proxy.type", 2); user_pref("network.proxy.type", 2);
user_pref("signon.rememberSignons", false); user_pref("signon.rememberSignons", false);
@@ -91,11 +91,11 @@ EOT
while ! curl "http://localhost" > /dev/null; do while ! curl "http://localhost" > /dev/null; do
sleep 1 sleep 1
done done
while ! /usr/lib/embassy/scripts/check-monitor; do while ! /usr/lib/startos/scripts/check-monitor; do
sleep 15 sleep 15
done done
( (
while /usr/lib/embassy/scripts/check-monitor; do while /usr/lib/startos/scripts/check-monitor; do
sleep 15 sleep 15
done done
killall firefox-esr killall firefox-esr

View File

@@ -13,7 +13,7 @@ fi
>&2 echo ' sudo rm /usr/local/bin/apt' >&2 echo ' sudo rm /usr/local/bin/apt'
>&2 echo >&2 echo
>&2 echo 'Otherwise, what you probably want to do is run:' >&2 echo 'Otherwise, what you probably want to do is run:'
>&2 echo ' sudo /usr/lib/embassy/scripts/chroot-and-upgrade' >&2 echo ' sudo /usr/lib/startos/scripts/chroot-and-upgrade'
>&2 echo 'You can run apt in this context to add packages to your system.' >&2 echo 'You can run apt in this context to add packages to your system.'
>&2 echo 'When you are done with your changes, type "exit" and the device will reboot into a system with the changes applied.' >&2 echo 'When you are done with your changes, type "exit" and the device will reboot into a system with the changes applied.'
>&2 echo 'This is still NOT RECOMMENDED if you don'"'"'t know what you are doing, but at least isn'"'"'t guaranteed to break things.' >&2 echo 'This is still NOT RECOMMENDED if you don'"'"'t know what you are doing, but at least isn'"'"'t guaranteed to break things.'

View File

@@ -14,7 +14,7 @@ while [ -n "$1" ]; do
done done
if [ ${#TO_INSTALL[@]} -ne 0 ]; then if [ ${#TO_INSTALL[@]} -ne 0 ]; then
/usr/lib/embassy/scripts/chroot-and-upgrade << EOF /usr/lib/startos/scripts/chroot-and-upgrade << EOF
apt-get update && apt-get install -y ${TO_INSTALL[@]} apt-get update && apt-get install -y ${TO_INSTALL[@]}
EOF EOF
fi fi

View File

@@ -60,12 +60,12 @@ sudo mount `partition_for ${OUTPUT_DEVICE} 2` $TMPDIR
sudo mkdir $TMPDIR/boot sudo mkdir $TMPDIR/boot
sudo mount `partition_for ${OUTPUT_DEVICE} 1` $TMPDIR/boot sudo mount `partition_for ${OUTPUT_DEVICE} 1` $TMPDIR/boot
sudo unsquashfs -f -d $TMPDIR startos.raspberrypi.squashfs sudo unsquashfs -f -d $TMPDIR startos.raspberrypi.squashfs
REAL_GIT_HASH=$(cat $TMPDIR/usr/lib/embassy/GIT_HASH.txt) REAL_GIT_HASH=$(cat $TMPDIR/usr/lib/startos/GIT_HASH.txt)
REAL_VERSION=$(cat $TMPDIR/usr/lib/embassy/VERSION.txt) REAL_VERSION=$(cat $TMPDIR/usr/lib/startos/VERSION.txt)
REAL_ENVIRONMENT=$(cat $TMPDIR/usr/lib/embassy/ENVIRONMENT.txt) REAL_ENVIRONMENT=$(cat $TMPDIR/usr/lib/startos/ENVIRONMENT.txt)
sudo sed -i 's| boot=embassy| init=/usr/lib/embassy/scripts/init_resize\.sh|' $TMPDIR/boot/cmdline.txt sudo sed -i 's| boot=embassy| init=/usr/lib/startos/scripts/init_resize\.sh|' $TMPDIR/boot/cmdline.txt
sudo cp ./build/raspberrypi/fstab $TMPDIR/etc/ sudo cp ./build/raspberrypi/fstab $TMPDIR/etc/
sudo cp ./build/raspberrypi/init_resize.sh $TMPDIR/usr/lib/embassy/scripts/init_resize.sh sudo cp ./build/raspberrypi/init_resize.sh $TMPDIR/usr/lib/startos/scripts/init_resize.sh
sudo umount $TMPDIR/boot sudo umount $TMPDIR/boot
sudo umount $TMPDIR sudo umount $TMPDIR
sudo losetup -d $OUTPUT_DEVICE sudo losetup -d $OUTPUT_DEVICE

View File

@@ -0,0 +1,9 @@
#!/bin/bash
for image in $(find /root/resources/eos/ -type f -name '*.squashfs' -mmin +240 -exec realpath {} \;); do
if ! mount | grep "^$image" > /dev/null; then
>&2 echo "Removing dangling image: $image"
rm $image
fi
done
find /root/resources/eos -type d -empty -delete

View File

@@ -0,0 +1,45 @@
#!/bin/bash
set -e
RUN_ID=$1
if [ -z "$RUN_ID" ]; then
>&2 echo usage: $0 '<run-id>'
exit 1
fi
TMP_DIR=/var/tmp/action-run-results/$RUN_ID
rm -rf $TMP_DIR
mkdir -p $TMP_DIR
cd $TMP_DIR
for arch in x86_64 x86_64-nonfree aarch64 aarch64-nonfree raspberrypi; do
gh run download -R Start9Labs/start-os $RUN_ID -n $arch.squashfs
done
VERSION=
HASH=
for file in $(ls *.squashfs); do
if [[ $file =~ ^startos-([0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?)-([a-f0-9]{7}(~[a-z-]+)?|unknown)_([a-z0-9_-]+).squashfs$ ]]; then
if [ -n "$VERSION" ] && [ "$VERSION" != "${BASH_REMATCH[1]}" ]; then
>&2 echo "VERSION MISMATCH: expected $VERSION got ${BASH_REMATCH[1]}"
exit 2
fi
if [ -n "$HASH" ] && [ "$HASH" != "${BASH_REMATCH[3]}" ]; then
>&2 echo "HASH MISMATCH: expected $HASH got ${BASH_REMATCH[3]}"
exit 3
fi
VERSION="${BASH_REMATCH[1]}"
HASH="${BASH_REMATCH[3]}"
fi
done
mkdir -p /root/resources/eos/$VERSION
rm -rf /root/resources/eos/$VERSION/$HASH
mv $TMP_DIR /root/resources/eos/$VERSION/$HASH
cd /root/resources/eos/$VERSION
setOsCommitHash $HASH

22
build/registry/resync.cgi Normal file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
declare -A params
while IFS='=' read -r -d '&' key value && [[ -n "$key" ]]; do
params["$key"]=$value
done <<<"${QUERY_STRING}&"
index_key="${params['key']}"
if [ -z "$index_key" ] || [ "$index_key" != "$(cat /var/www/index_key.txt)" ]; then
echo "HTTP/1.1 401 UNAUTHORIZED"
echo "Content-Type: text/html"
echo
echo "UNAUTHORIZED"
exit
fi
touch /tmp/resync
echo "HTTP/1.1 200 OK"
echo "Content-Type: text/html"
echo
echo "OK: Upload successful"

View File

@@ -6,12 +6,14 @@
# Then we are going to make sure that each of these files is then put on the rsyncd server # Then we are going to make sure that each of these files is then put on the rsyncd server
# so the embassies can pull them down # so the embassies can pull them down
date >> /var/log/resyncRsyncRegistry.runlog
cat > /etc/rsyncd.conf << RD cat > /etc/rsyncd.conf << RD
uid = root uid = root
gid = root gid = root
use chroot = yes use chroot = yes
max connections = 50 max connections = 4
pid file = /var/run/rsyncd.pid pid file = /var/run/rsyncd.pid
exclude = lost+found/ exclude = lost+found/
timeout = 900 timeout = 900
@@ -27,7 +29,7 @@ do
filename=${dir##*/} filename=${dir##*/}
version=$(echo $directory | sed -r 's/.*\///') version=$(echo $directory | sed -r 's/.*\///')
version_dir="/srv/rsync/$version" version_dir="/srv/rsync/$version"
type=$(echo "$filename" | sed -r "s/^.*?\.(\w+)\.squashfs$/\1/") type=$(echo "$filename" | sed -r "s/^.*?\.([a-z0-9_-]+)\.squashfs$/\1/")
new_dir="$version_dir/$type" new_dir="$version_dir/$type"

View File

@@ -0,0 +1,39 @@
#!/bin/bash
# Get the current directory
PWD=$(pwd)
HASH=$1
if [ -z "$HASH" ]; then
>&2 echo "usage: setOsCommitHash <hash>"
exit 1
fi
# Define the expected pattern for the directory
pattern="/root/resources/eos/"
# Check if the current directory matches the pattern
if [[ $PWD =~ ^$pattern([0-9.]+)$ ]]; then
# Extract the version number from the directory path
version="${BASH_REMATCH[1]}"
else
>&2 echo "MUST BE IN OS VERSION DIRECTORY"
exit 1
fi
if ! [ -d "$HASH" ]; then
>&2 echo "$HASH: No such directory"
exit 1
fi
for file in $(ls $HASH/startos-$version-${HASH}_*.squashfs); do
if [[ $file =~ ^$HASH/startos-$version-${HASH}_([a-z0-9_-]+).squashfs$ ]]; then
arch="${BASH_REMATCH[1]}"
echo "Found arch $arch"
umount /srv/rsync/$version/$arch
rm eos.$arch.squashfs
ln -s $file eos.$arch.squashfs
fi
done
resyncRsyncRegistry

48
build/registry/upload.cgi Normal file
View File

@@ -0,0 +1,48 @@
#!/bin/bash
declare -A params
while IFS='=' read -r -d '&' key value && [[ -n "$key" ]]; do
params["$key"]=$value
done <<<"${QUERY_STRING}&"
index_key="${params['key']}"
if [ -z "$index_key" ] || [ "$index_key" != "$(cat /var/www/index_key.txt)" ]; then
echo "HTTP/1.1 401 UNAUTHORIZED"
echo "Content-Type: text/html"
echo
echo "UNAUTHORIZED"
exit
fi
git_hash="${params['gitHash']}"
version="${params['version']}"
platform="${params['platform']}"
shasum="${params['shasum']}"
if [ -z "$git_hash" ] || [ -z "$version" ] || [ -z "$platform" ] || [ -z "$shasum" ]; then
echo "HTTP/1.1 400 BAD REQUEST"
echo "Content-Type: text/html"
echo
echo "BAD REQUEST: missing param"
exit
fi
tmp_file=$(mktemp /var/tmp/tmp.XXXXXXXXXX.squashfs)
cat > $tmp_file
if ! sha256sum $tmp_file | grep "$shasum"; then
rm $tmp_file
echo "HTTP/1.1 400 BAD REQUEST"
echo "Content-Type: text/html"
echo
echo "BAD REQUEST: shasum mismatch"
fi
mkdir -p /var/www/resources/eos/${version}/${git_hash}
mv $tmp_file /var/www/resources/eos/${version}/${git_hash}/startos-${version}-${git_hash}_${platform}.squashfs
rm /var/www/resources/eos/${version}/eos.${platform}.squashfs
ln -rs /var/www/resources/eos/${version}/${git_hash}/startos-${version}-${git_hash}_${platform}.squashfs /var/www/resources/eos/${version}/eos.${platform}.squashfs
echo "HTTP/1.1 200 OK"
echo "Content-Type: text/html"
echo
echo "OK: Upload successful"

View File

@@ -1,6 +1,10 @@
#!/bin/bash #!/bin/bash
GIT_HASH="$(git describe --always --abbrev=40 --dirty=-modified)" if [ "$GIT_BRANCH_AS_HASH" != 1 ]; then
GIT_HASH="$(git describe --always --abbrev=40 --dirty=-modified)"
else
GIT_HASH="@$(git rev-parse --abbrev-ref HEAD)"
fi
if ! [ -f ./GIT_HASH.txt ] || [ "$(cat ./GIT_HASH.txt)" != "$GIT_HASH" ]; then if ! [ -f ./GIT_HASH.txt ] || [ "$(cat ./GIT_HASH.txt)" != "$GIT_HASH" ]; then
echo -n "$GIT_HASH" > ./GIT_HASH.txt echo -n "$GIT_HASH" > ./GIT_HASH.txt

8
check-platform.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
if ! [ -f ./PLATFORM.txt ] || [ "$(cat ./PLATFORM.txt)" != "$PLATFORM" ] && [ -n "$PLATFORM" ]; then
>&2 echo "Updating PLATFORM.txt to \"$PLATFORM\""
echo -n "$PLATFORM" > ./PLATFORM.txt
fi
echo -n ./PLATFORM.txt

View File

@@ -8,10 +8,10 @@ fi
if [ -f /usr/sbin/grub-probe ]; then if [ -f /usr/sbin/grub-probe ]; then
mv /usr/sbin/grub-probe /usr/sbin/grub-probe-default mv /usr/sbin/grub-probe /usr/sbin/grub-probe-default
ln -s /usr/lib/embassy/scripts/grub-probe-eos /usr/sbin/grub-probe ln -s /usr/lib/startos/scripts/grub-probe-eos /usr/sbin/grub-probe
fi fi
cp /usr/lib/embassy/scripts/embassy-initramfs-module /etc/initramfs-tools/scripts/embassy cp /usr/lib/startos/scripts/embassy-initramfs-module /etc/initramfs-tools/scripts/embassy
if ! grep overlay /etc/initramfs-tools/modules > /dev/null; then if ! grep overlay /etc/initramfs-tools/modules > /dev/null; then
echo overlay >> /etc/initramfs-tools/modules echo overlay >> /etc/initramfs-tools/modules
@@ -21,6 +21,7 @@ update-initramfs -u -k all
if [ -f /etc/default/grub ]; then if [ -f /etc/default/grub ]; then
sed -i '/\(^\|#\)GRUB_CMDLINE_LINUX=/c\GRUB_CMDLINE_LINUX="boot=embassy"' /etc/default/grub sed -i '/\(^\|#\)GRUB_CMDLINE_LINUX=/c\GRUB_CMDLINE_LINUX="boot=embassy"' /etc/default/grub
sed -i '/\(^\|#\)GRUB_DISTRIBUTOR=/c\GRUB_DISTRIBUTOR="StartOS v$(cat /usr/lib/startos/VERSION.txt)"' /etc/default/grub
fi fi
# change timezone # change timezone
@@ -46,6 +47,7 @@ dns=systemd-resolved
[ifupdown] [ifupdown]
managed=true managed=true
EOF EOF
$SYSTEMCTL enable startd.service
$SYSTEMCTL enable systemd-resolved.service $SYSTEMCTL enable systemd-resolved.service
$SYSTEMCTL enable systemd-networkd-wait-online.service $SYSTEMCTL enable systemd-networkd-wait-online.service
$SYSTEMCTL enable ssh.service $SYSTEMCTL enable ssh.service
@@ -70,20 +72,20 @@ fi
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config
sed -i 's/Restart=on-failure/Restart=always/g' /lib/systemd/system/tor@default.service sed -i 's/Restart=on-failure/Restart=always/g' /lib/systemd/system/tor@default.service
sed -i 's/ExecStart=\/usr\/bin\/dockerd/ExecStart=\/usr\/bin\/dockerd --exec-opt native.cgroupdriver=systemd/g' /lib/systemd/system/docker.service
sed -i '/\(^\|#\)entries-per-entry-group-max=/c\entries-per-entry-group-max=128' /etc/avahi/avahi-daemon.conf sed -i '/\(^\|#\)entries-per-entry-group-max=/c\entries-per-entry-group-max=128' /etc/avahi/avahi-daemon.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
sed -i '/\(^\|#\)ForwardToSyslog=/c\ForwardToSyslog=no' /etc/systemd/journald.conf sed -i '/\(^\|#\)ForwardToSyslog=/c\ForwardToSyslog=no' /etc/systemd/journald.conf
sed -i '/^\s*#\?\s*issue_discards\s*=\s*/c\issue_discards = 1' /etc/lvm/lvm.conf sed -i '/^\s*#\?\s*issue_discards\s*=\s*/c\issue_discards = 1' /etc/lvm/lvm.conf
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << EOF if cat /usr/lib/startos/ENVIRONMENT.txt | grep '(^|-)docker(-|$)'; then
{ sed -i 's/ExecStart=\/usr\/bin\/dockerd/ExecStart=\/usr\/bin\/dockerd --exec-opt native.cgroupdriver=systemd/g' /lib/systemd/system/docker.service
"storage-driver": "overlay2" mkdir -p /etc/docker
} echo '{ "storage-driver": "overlay2" }' > /etc/docker/daemon.json
EOF else
podman network create -d bridge --subnet 172.18.0.1/24 --opt com.docker.network.bridge.name=br-start9 start9 podman network create -d bridge --subnet 172.18.0.1/24 --opt com.docker.network.bridge.name=br-start9 start9
fi
mkdir -p /etc/nginx/ssl mkdir -p /etc/nginx/ssl
# fix to suppress docker warning, fixed in 21.xx release of docker cli: https://github.com/docker/cli/pull/2934 # fix to suppress docker warning, fixed in 21.xx release of docker cli: https://github.com/docker/cli/pull/2934
@@ -100,7 +102,7 @@ CookieAuthentication 1
EOF EOF
rm -rf /var/lib/tor/* rm -rf /var/lib/tor/*
ln -sf /usr/lib/embassy/scripts/tor-check.sh /usr/bin/tor-check ln -sf /usr/lib/startos/scripts/tor-check.sh /usr/bin/tor-check
echo "fs.inotify.max_user_watches=1048576" > /etc/sysctl.d/97-embassy.conf echo "fs.inotify.max_user_watches=1048576" > /etc/sysctl.d/97-embassy.conf
@@ -113,9 +115,9 @@ dpkg-reconfigure --frontend noninteractive locales
groupadd embassy groupadd embassy
ln -s /usr/lib/embassy/scripts/dhclient-exit-hook /etc/dhcp/dhclient-exit-hooks.d/embassy ln -s /usr/lib/startos/scripts/dhclient-exit-hook /etc/dhcp/dhclient-exit-hooks.d/embassy
rm -f /etc/motd rm -f /etc/motd
ln -sf /usr/lib/embassy/motd /etc/update-motd.d/00-embassy ln -sf /usr/lib/startos/motd /etc/update-motd.d/00-embassy
chmod -x /etc/update-motd.d/* chmod -x /etc/update-motd.d/*
chmod +x /etc/update-motd.d/00-embassy chmod +x /etc/update-motd.d/00-embassy

48
dpkg-build.sh Executable file
View File

@@ -0,0 +1,48 @@
#!/bin/bash
set -e
cd "$(dirname "${BASH_SOURCE[0]}")"
BASENAME=$(./basename.sh)
VERSION=$(cat ./VERSION.txt)
if [ "$PLATFORM" = "x86_64" ] || [ "$PLATFORM" = "x86_64-nonfree" ]; then
DEB_ARCH=amd64
elif [ "$PLATFORM" = "aarch64" ] || [ "$PLATFORM" = "aarch64-nonfree" ] || [ "$PLATFORM" = "raspberrypi" ]; then
DEB_ARCH=arm64
else
DEB_ARCH="$PLATFORM"
fi
rm -rf dpkg-workdir/$BASENAME
mkdir -p dpkg-workdir/$BASENAME
make install DESTDIR=dpkg-workdir/$BASENAME
DEPENDS=$(cat dpkg-workdir/$BASENAME/usr/lib/startos/depends | tr $'\n' ',' | sed 's/,,\+/,/g' | sed 's/,$//')
CONFLICTS=$(cat dpkg-workdir/$BASENAME/usr/lib/startos/conflicts | tr $'\n' ',' | sed 's/,,\+/,/g' | sed 's/,$//')
cp -r debian dpkg-workdir/$BASENAME/DEBIAN
cat > dpkg-workdir/$BASENAME/DEBIAN/control << EOF
Package: startos
Version: ${VERSION}
Section: unknown
Priority: required
Maintainer: Aiden McClelland <aiden@start9.com>
Homepage: https://start9.com
Architecture: ${DEB_ARCH}
Multi-Arch: foreign
Depends: ${DEPENDS}
Conflicts: ${CONFLICTS}
Description: StartOS Debian Package
EOF
cd dpkg-workdir/$BASENAME
find . -type f -not -path "./DEBIAN/*" -exec md5sum {} \; | sort -k 2 | sed 's/\.\/\(.*\)/\1/' > DEBIAN/md5sums
cd ../..
cd dpkg-workdir
dpkg-deb --root-owner-group -b $BASENAME
mkdir -p ../results
mv $BASENAME.deb ../results/$BASENAME.deb
rm -rf $BASENAME

View File

@@ -1,8 +1,6 @@
{ {
"useMocks": true, "useMocks": true,
"enableWidgets": false, "enableWidgets": false,
"packageArch": "aarch64",
"osArch": "raspberrypi",
"ui": { "ui": {
"api": { "api": {
"url": "rpc", "url": "rpc",

13319
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,8 +47,9 @@
"@maskito/angular": "^0.10.0", "@maskito/angular": "^0.10.0",
"@maskito/core": "^0.10.0", "@maskito/core": "^0.10.0",
"@materia-ui/ngx-monaco-editor": "^6.0.0", "@materia-ui/ngx-monaco-editor": "^6.0.0",
"@start9labs/argon2": "^0.1.0", "@start9labs/argon2": "^0.2.2",
"@start9labs/emver": "^0.1.5", "@start9labs/emver": "^0.1.5",
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc5",
"@taiga-ui/addon-charts": "3.28.0", "@taiga-ui/addon-charts": "3.28.0",
"@taiga-ui/cdk": "3.28.0", "@taiga-ui/cdk": "3.28.0",
"@taiga-ui/core": "3.28.0", "@taiga-ui/core": "3.28.0",
@@ -76,7 +77,6 @@
"patch-db-client": "file: ../../../patch-db/client", "patch-db-client": "file: ../../../patch-db/client",
"pbkdf2": "^3.1.2", "pbkdf2": "^3.1.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc5",
"swiper": "^8.2.4", "swiper": "^8.2.4",
"ts-matches": "^5.2.1", "ts-matches": "^5.2.1",
"tslib": "^2.3.0", "tslib": "^2.3.0",

View File

@@ -27,7 +27,6 @@
[type]="!unmasked1 ? 'password' : 'text'" [type]="!unmasked1 ? 'password' : 'text'"
placeholder="Enter Password" placeholder="Enter Password"
(ionChange)="validate()" (ionChange)="validate()"
maxlength="64"
></ion-input> ></ion-input>
<ion-button fill="clear" color="light" (click)="unmasked1 = !unmasked1"> <ion-button fill="clear" color="light" (click)="unmasked1 = !unmasked1">
<ion-icon <ion-icon
@@ -48,7 +47,6 @@
[type]="!unmasked2 ? 'password' : 'text'" [type]="!unmasked2 ? 'password' : 'text'"
placeholder="Retype Password" placeholder="Retype Password"
(ionChange)="checkVer()" (ionChange)="checkVer()"
maxlength="64"
></ion-input> ></ion-input>
<ion-button <ion-button
fill="clear" fill="clear"

View File

@@ -37,7 +37,7 @@
<p> <p>
Download your server's Root CA and Download your server's Root CA and
<a <a
href="https://docs.start9.com/latest/user-manual/connecting/connecting-lan" href="https://docs.start9.com/0.3.5.x/user-manual/connecting-lan"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
style="color: #6866cc; font-weight: bold; text-decoration: none" style="color: #6866cc; font-weight: bold; text-decoration: none"
@@ -104,7 +104,7 @@
<span style="font-weight: bold">Note:</span> <span style="font-weight: bold">Note:</span>
This address will only work from a Tor-enabled browser. This address will only work from a Tor-enabled browser.
<a <a
href="https://docs.start9.com/latest/user-manual/connecting/connecting-tor" href="https://docs.start9.com/0.3.5.x/user-manual/connecting-tor"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
style="color: #6866cc; font-weight: bold; text-decoration: none" style="color: #6866cc; font-weight: bold; text-decoration: none"

View File

@@ -8,23 +8,21 @@
<ion-card> <ion-card>
<ion-row class="ion-align-items-center"> <ion-row class="ion-align-items-center">
<ion-col responsiveCol sizeXs="12" class="ion-text-center"> <ion-col responsiveCol sizeXs="12" class="ion-text-center">
<div class="inline" style="margin-bottom: 3rem"> <div class="inline mb-12">
<ion-icon <ion-icon
name="checkmark-circle-outline" name="checkmark-circle-outline"
color="success" color="success"
></ion-icon> ></ion-icon>
<h1>Setup Complete!</h1> <h1>Setup Complete!</h1>
</div> </div>
<div class="card-container"> <ion-button
<ion-card id="exit" (click)="exitKiosk()"> shape="round"
<div class="container"> class="login-button mb-12"
<div class="inline"> (click)="exitKiosk()"
<p>Continue to login</p> >
<ion-icon name="log-in-outline"></ion-icon> Continue to Login
</div> <ion-icon name="log-in-outline" slot="end"></ion-icon>
</div> </ion-button>
</ion-card>
</div>
</ion-col> </ion-col>
</ion-row> </ion-row>
</ion-card> </ion-card>
@@ -34,8 +32,8 @@
<ion-card *ngIf="lanAddress"> <ion-card *ngIf="lanAddress">
<ion-row class="ion-align-items-center"> <ion-row class="ion-align-items-center">
<ion-col responsiveCol sizeXs="12" class="ion-text-center"> <ion-col responsiveCol sizeXs="12" class="ion-text-center">
<div style="margin-bottom: 4rem"> <div class="mb-12">
<div class="inline"> <div class="inline-container setup">
<ion-icon <ion-icon
name="checkmark-circle-outline" name="checkmark-circle-outline"
color="success" color="success"
@@ -52,35 +50,40 @@
<div class="card-container"> <div class="card-container">
<ion-card id="information" (click)="download()"> <ion-card id="information" (click)="download()">
<ion-card-content> <ion-card-content>
<ion-card-title> <ion-card-title>Download address info</ion-card-title>
Download permanent address info
</ion-card-title>
<p> <p>
start.local was for setup purposes only. It will no start.local was for setup purposes only. It will no
longer work. longer work.
</p> </p>
</ion-card-content> </ion-card-content>
<ion-footer> <ion-footer>
<div class="container"> <div class="inline-container">
<div class="inline"> <p class="action-text">Download</p>
<p>Download</p> <ion-icon slot="end" name="download-outline"></ion-icon>
<ion-icon name="download-outline"></ion-icon>
</div>
</div> </div>
</ion-footer> </ion-footer>
</ion-card> </ion-card>
<ion-card <ion-card
[disabled]="disableLogin"
id="launch" id="launch"
[disabled]="disableLogin"
href="{{ lanAddress }}" href="{{ lanAddress }}"
target="_blank" target="_blank"
> >
<div class="container"> <ion-card-content>
<div class="inline"> <ion-card-title>Trust your Root CA</ion-card-title>
<p>Login to StartOS</p> <p>
<ion-icon name="open-outline"></ion-icon> In the new tab, follow instructions to trust your
server's Root CA and log in.
</p>
</ion-card-content>
<ion-footer>
<div class="container">
<div class="inline-container">
<p class="action-text">Open</p>
<ion-icon slot="end" name="open-outline"></ion-icon>
</div>
</div> </div>
</div> </ion-footer>
</ion-card> </ion-card>
</div> </div>
</ion-col> </ion-col>

View File

@@ -18,19 +18,24 @@ ion-content {
ion-grid { ion-grid {
max-width: 760px; max-width: 760px;
height: 100%;
} }
.grid-center-wrapper { .inline-container {
height: 100%;
width: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.card-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 1rem;
}
ion-card { ion-card {
padding: 3rem; padding: 2.4rem;
h1 { h1 {
color: var(--ion-color-success); color: var(--ion-color-success);
@@ -44,14 +49,14 @@ ion-card {
margin-bottom: 2rem; margin-bottom: 2rem;
} }
// download info card
ion-card { ion-card {
max-width: 91%; min-height: 260px;
min-width: 91%; width: 80%;
background: #615F5F; background: #615F5F;
color: var(--ion-text-color); color: var(--ion-text-color);
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
border-radius: 44px; border-radius: 44px;
margin: auto;
text-align: left; text-align: left;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
@@ -70,14 +75,6 @@ ion-card {
font-size: 1.3rem; font-size: 1.3rem;
} }
ion-card-content {
padding-bottom: 4rem;
p {
padding: 1rem 0;
}
}
ion-footer { ion-footer {
position: absolute; position: absolute;
bottom: 10px; bottom: 10px;
@@ -100,19 +97,24 @@ ion-card {
} }
} }
.container { .login-button {
display: flex; --background: var(--color-accent);
justify-content: center; --padding-bottom: 2.5rem;
align-items: center; --padding-top: 2.5rem;
} --padding-start: 2.5rem;
--padding-end: 2.5rem;
--border-radius: 44px;
font-size: 1.4rem !important;
font-weight: bold;
text-transform: none;
letter-spacing: normal;
transition: all 350ms ease;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
#exit { &:hover {
background: var(--color-accent); transition-property: transform;
height: 100%; transform: scale(1.05);
transition-delay: 40ms;
.container p {
font-size: 1.4rem !important;
font-weight: bold;
} }
ion-icon { ion-icon {
@@ -120,40 +122,62 @@ ion-card {
} }
} }
#launch { .launch-button {
background: var(--alt-blue); --background: var(--alt-blue);
height: 100%;
.container p {
font-size: 1.4rem !important;
font-weight: bold;
}
ion-icon {
font-size: 1.7rem;
}
} }
#information:after { #information:after, #launch:after {
content: ''; content: '';
position: absolute; position: absolute;
left: 0; left: 0;
top: 80%; top: 79%;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: var(--color-accent); background: var(--color-accent);
} }
#launch:after {
background: var(--alt-blue);
}
} }
.card-container { .mb-12 {
display: flex; margin-bottom: 3rem;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 2rem;
} }
.emphasis-warn { .pb-2 {
font-weight: 600; padding-bottom: 0.5rem;
color: var(--ion-color-warning); }
.pt-1 {
padding-top: 0.25rem;
}
.action-text {
font-variant-caps: all-small-caps;
padding-right: 0.5rem;
font-size: 1.5rem !important;
letter-spacing: 0.03rem;
padding-bottom: 0.1rem;
}
@media (max-width: 700px) {
.setup {
flex-direction: column;
}
ion-card {
ion-card {
width: 100%;
padding-bottom: unset;
}
#information:after {
top: 84%;
}
#launch:after {
top: 85%;
}
}
} }

View File

@@ -45,18 +45,7 @@ export class SuccessPage {
async ngAfterViewInit() { async ngAfterViewInit() {
this.ngZone.runOutsideAngular(() => this.initMatrix()) this.ngZone.runOutsideAngular(() => this.initMatrix())
try { setTimeout(() => this.complete(), 1000)
const ret = await this.api.complete()
if (!this.isKiosk) {
this.torAddress = ret['tor-address']
this.lanAddress = ret['lan-address'].replace(/^https:/, 'http:')
this.cert = ret['root-ca']
await this.api.exit()
}
} catch (e: any) {
await this.errCtrl.present(e)
}
} }
download() { download() {
@@ -83,6 +72,21 @@ export class SuccessPage {
this.api.exit() this.api.exit()
} }
private async complete() {
try {
const ret = await this.api.complete()
if (!this.isKiosk) {
this.torAddress = ret['tor-address'].replace(/^https:/, 'http:')
this.lanAddress = ret['lan-address'].replace(/^https:/, 'http:')
this.cert = ret['root-ca']
await this.api.exit()
}
} catch (e: any) {
await this.errCtrl.present(e)
}
}
private initMatrix() { private initMatrix() {
this.ctx = this.canvas.nativeElement.getContext('2d')! this.ctx = this.canvas.nativeElement.getContext('2d')!
this.canvas.nativeElement.width = window.innerWidth this.canvas.nativeElement.width = window.innerWidth

View File

@@ -218,7 +218,7 @@ ion-toast {
* { * {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
padding: 0.3rem; padding-left: 0px 0.3rem;
} }
} }

View File

@@ -1,6 +1,6 @@
import { Directive, HostListener, Inject } from '@angular/core' import { Directive, HostListener, Inject } from '@angular/core'
import { DOCUMENT } from '@angular/common' import { DOCUMENT } from '@angular/common'
import { debounce } from '@start9labs/shared' import { debounce } from '../../util/misc.util'
@Directive({ @Directive({
selector: '[appEnter]', selector: '[appEnter]',

View File

@@ -27,6 +27,8 @@ export * from './directives/responsive-col/responsive-col.module'
export * from './directives/responsive-col/responsive-col-viewport.directive' export * from './directives/responsive-col/responsive-col-viewport.directive'
export * from './directives/safe-links/safe-links.directive' export * from './directives/safe-links/safe-links.directive'
export * from './directives/safe-links/safe-links.module' export * from './directives/safe-links/safe-links.module'
export * from './directives/enter/enter.directive'
export * from './directives/enter/enter.module'
export * from './mocks/get-setup-status' export * from './mocks/get-setup-status'

View File

@@ -53,9 +53,9 @@ export function getErrorMessage(
} else if (e.code === 0) { } else if (e.code === 0) {
message = message =
'Request Error. Your browser blocked the request. This is usually caused by a corrupt browser cache or an overly aggressive ad blocker. Please clear your browser cache and/or adjust your ad blocker and try again' 'Request Error. Your browser blocked the request. This is usually caused by a corrupt browser cache or an overly aggressive ad blocker. Please clear your browser cache and/or adjust your ad blocker and try again'
link = 'https://docs.start9.com/0.3.5.x/support/common-issues#request-error'
} else if (!e.message) { } else if (!e.message) {
message = 'Unknown Error' message = 'Unknown Error'
link = 'https://docs.start9.com/latest/support/faq'
} else { } else {
message = e.message message = e.message
} }

View File

@@ -1,6 +1,4 @@
export type WorkspaceConfig = { export type WorkspaceConfig = {
packageArch: 'aarch64' | 'x86_64'
osArch: 'aarch64' | 'x86_64' | 'raspberrypi'
gitHash: string gitHash: string
useMocks: boolean useMocks: boolean
enableWidgets: boolean enableWidgets: boolean

View File

@@ -26,10 +26,7 @@
type="overlay" type="overlay"
side="end" side="end"
class="right-menu container" class="right-menu container"
[class.container_offline]=" [class.container_offline]="offline$ | async"
(authService.isVerified$ | async) &&
!(connection.connected$ | async)
"
[class.right-menu_hidden]="!drawer.open" [class.right-menu_hidden]="!drawer.open"
[style.--side-width.px]="drawer.width" [style.--side-width.px]="drawer.width"
> >
@@ -47,10 +44,7 @@
[responsiveColViewport]="viewport" [responsiveColViewport]="viewport"
id="main-content" id="main-content"
class="container" class="container"
[class.container_offline]=" [class.container_offline]="offline$ | async"
(authService.isVerified$ | async) &&
!(connection.connected$ | async)
"
> >
<ion-content <ion-content
#viewport="viewport" #viewport="viewport"

View File

@@ -1,5 +1,5 @@
import { Component, inject, OnDestroy } from '@angular/core' import { Component, inject, OnDestroy } from '@angular/core'
import { merge } from 'rxjs' import { combineLatest, map, merge, startWith } from 'rxjs'
import { AuthService } from './services/auth.service' import { AuthService } from './services/auth.service'
import { SplitPaneTracker } from './services/split-pane.service' import { SplitPaneTracker } from './services/split-pane.service'
import { PatchDataService } from './services/patch-data.service' import { PatchDataService } from './services/patch-data.service'
@@ -25,6 +25,19 @@ export class AppComponent implements OnDestroy {
readonly sidebarOpen$ = this.splitPane.sidebarOpen$ readonly sidebarOpen$ = this.splitPane.sidebarOpen$
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$ readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
readonly theme$ = inject(THEME) readonly theme$ = inject(THEME)
readonly offline$ = combineLatest([
this.authService.isVerified$,
this.connection.connected$,
this.patch
.watch$('server-info', 'status-info')
.pipe(startWith({ restarting: false, 'shutting-down': false })),
]).pipe(
map(
([verified, connected, status]) =>
verified &&
(!connected || status.restarting || status['shutting-down']),
),
)
constructor( constructor(
private readonly titleService: Title, private readonly titleService: Title,

View File

@@ -11,11 +11,12 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor' import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
import { import {
MarkdownModule,
DarkThemeModule, DarkThemeModule,
EnterModule,
LightThemeModule,
MarkdownModule,
ResponsiveColModule, ResponsiveColModule,
SharedPipesModule, SharedPipesModule,
LightThemeModule,
} from '@start9labs/shared' } from '@start9labs/shared'
import { AppComponent } from './app.component' import { AppComponent } from './app.component'
@@ -24,7 +25,6 @@ import { OSWelcomePageModule } from './common/os-welcome/os-welcome.module'
import { PreloaderModule } from './app/preloader/preloader.module' import { PreloaderModule } from './app/preloader/preloader.module'
import { FooterModule } from './app/footer/footer.module' import { FooterModule } from './app/footer/footer.module'
import { MenuModule } from './app/menu/menu.module' import { MenuModule } from './app/menu/menu.module'
import { EnterModule } from './app/enter/enter.module'
import { APP_PROVIDERS } from './app.providers' import { APP_PROVIDERS } from './app.providers'
import { PatchDbModule } from './services/patch-db/patch-db.module' import { PatchDbModule } from './services/patch-db/patch-db.module'
import { ToastContainerModule } from './common/toast-container/toast-container.module' import { ToastContainerModule } from './common/toast-container/toast-container.module'

View File

@@ -1,6 +1,8 @@
import { ChangeDetectionStrategy, Component } from '@angular/core' import { ChangeDetectionStrategy, Component } from '@angular/core'
import { PatchDB } from 'patch-db-client'
import { combineLatest, map, Observable, startWith } from 'rxjs' import { combineLatest, map, Observable, startWith } from 'rxjs'
import { ConnectionService } from 'src/app/services/connection.service' import { ConnectionService } from 'src/app/services/connection.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
@Component({ @Component({
selector: 'connection-bar', selector: 'connection-bar',
@@ -19,8 +21,11 @@ export class ConnectionBarComponent {
}> = combineLatest([ }> = combineLatest([
this.connectionService.networkConnected$, this.connectionService.networkConnected$,
this.websocket$.pipe(startWith(false)), this.websocket$.pipe(startWith(false)),
this.patch
.watch$('server-info', 'status-info')
.pipe(startWith({ restarting: false, 'shutting-down': false })),
]).pipe( ]).pipe(
map(([network, websocket]) => { map(([network, websocket, status]) => {
if (!network) if (!network)
return { return {
message: 'No Internet', message: 'No Internet',
@@ -35,6 +40,20 @@ export class ConnectionBarComponent {
icon: 'cloud-offline-outline', icon: 'cloud-offline-outline',
dots: true, dots: true,
} }
if (status['shutting-down'])
return {
message: 'Shutting Down',
color: 'dark',
icon: 'power',
dots: true,
}
if (status.restarting)
return {
message: 'Restarting',
color: 'dark',
icon: 'power',
dots: true,
}
return { return {
message: 'Connected', message: 'Connected',
@@ -45,5 +64,8 @@ export class ConnectionBarComponent {
}), }),
) )
constructor(private readonly connectionService: ConnectionService) {} constructor(
private readonly connectionService: ConnectionService,
private readonly patch: PatchDB<DataModel>,
) {}
} }

View File

@@ -22,11 +22,17 @@
<ion-label class="label montserrat" routerLinkActive="label_selected"> <ion-label class="label montserrat" routerLinkActive="label_selected">
{{ page.title }} {{ page.title }}
</ion-label> </ion-label>
<ion-icon
*ngIf="page.url === '/system' && (warning$ | async)"
color="warning"
size="small"
name="warning"
></ion-icon>
<ion-icon <ion-icon
*ngIf="page.url === '/system' && (showEOSUpdate$ | async)" *ngIf="page.url === '/system' && (showEOSUpdate$ | async)"
color="success" color="success"
size="small" size="small"
name="rocket-outline" name="rocket"
></ion-icon> ></ion-icon>
<ion-badge <ion-badge
*ngIf="page.url === '/updates' && (updateCount$ | async) as updateCount" *ngIf="page.url === '/updates' && (updateCount$ | async) as updateCount"

View File

@@ -11,7 +11,9 @@ import {
filter, filter,
first, first,
map, map,
merge,
Observable, Observable,
of,
pairwise, pairwise,
startWith, startWith,
switchMap, switchMap,
@@ -22,6 +24,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
import { SplitPaneTracker } from 'src/app/services/split-pane.service' import { SplitPaneTracker } from 'src/app/services/split-pane.service'
import { Emver, THEME } from '@start9labs/shared' import { Emver, THEME } from '@start9labs/shared'
import { ConnectionService } from 'src/app/services/connection.service' import { ConnectionService } from 'src/app/services/connection.service'
import { ConfigService } from 'src/app/services/config.service'
@Component({ @Component({
selector: 'app-menu', selector: 'app-menu',
@@ -112,6 +115,11 @@ export class MenuComponent {
readonly theme$ = inject(THEME) readonly theme$ = inject(THEME)
readonly warning$ = merge(
of(this.config.isTorHttp()),
this.patch.watch$('server-info', 'ntp-synced').pipe(map(synced => !synced)),
)
constructor( constructor(
private readonly patch: PatchDB<DataModel>, private readonly patch: PatchDB<DataModel>,
private readonly eosService: EOSService, private readonly eosService: EOSService,
@@ -120,5 +128,6 @@ export class MenuComponent {
private readonly splitPane: SplitPaneTracker, private readonly splitPane: SplitPaneTracker,
private readonly emver: Emver, private readonly emver: Emver,
private readonly connectionService: ConnectionService, private readonly connectionService: ConnectionService,
private readonly config: ConfigService,
) {} ) {}
} }

View File

@@ -1,106 +1,102 @@
<ion-grid class="grid-wiz"> <div class="center-container">
<img width="60px" height="60px" src="/assets/img/icon.png" alt="StartOS" /> <ng-container *ngIf="!caTrusted; else trusted">
<ion-row> <ion-card id="untrusted" class="text-center">
<ion-col class="ion-text-center">
<ion-icon name="lock-closed-outline" class="wiz-icon"></ion-icon> <ion-icon name="lock-closed-outline" class="wiz-icon"></ion-icon>
</ion-col> <h1>Trust Your Root CA</h1>
</ion-row>
<ion-row>
<ion-col class="ion-text-center">
<h2><b>Trust your Root Certificate Authority (CA)</b></h2>
<p> <p>
Download and trust your server's Root CA to establish secure, encrypted Download and trust your server's Root Certificate Authority to establish
( a secure (HTTPS) connection. You will need to repeat this on every
<b>HTTPS</b> device you use to connect to your server.
) connections with your server
</p> </p>
</ion-col> <ol>
</ion-row> <li>
<ion-row> <b>Bookmark this page</b>
<ion-col sizeXs="12" sizeLg="4"> - Save this page so you can access it later. You can also find the
<div class="wiz-card"> address in the
<ion-row class="ion-justify-content-between"> <code>StartOS-info.html</code>
<b class="wiz-step">1</b> file downloaded at the end of initial setup.
<tui-tooltip </li>
content="Your server uses its Root CA to generate SSL/TLS certificates for itself and its installed services. These certificates are used to encrypt network traffic with your client devices." <li>
direction="right" <b>Download your server's Root CA</b>
></tui-tooltip> - Your server uses its Root CA to generate SSL/TLS certificates for
</ion-row> itself and installed services. These certificates are then used to
<div class="ion-text-center"> encrypt network traffic with your client devices.
<h2>Download Root CA</h2> <br />
<p>Download your server's Root CA</p> <ion-button
</div> strong
<ion-button class="wiz-card-button" shape="round" (click)="download()"> size="small"
<ion-icon slot="start" name="download-outline"></ion-icon> shape="round"
Download color="tertiary"
</ion-button> (click)="download()"
</div> >
</ion-col> Download
<ion-col sizeXs="12" sizeLg="4"> <ion-icon slot="end" name="download-outline"></ion-icon>
<div class="wiz-card" [class.disabled]="!downloadClicked"> </ion-button>
<ion-row class="ion-justify-content-between"> </li>
<b class="wiz-step">2</b> <li>
<tui-tooltip <b>Trust your server's Root CA</b>
content="By trusting your server's Root CA, your device can verify the authenticity of its encrypted communications with your server and installed services. You will need to trust the Root CA on every device used to connect to your server." - Follow instructions for your OS. By trusting your server's Root CA,
direction="right" your device can verify the authenticity of encrypted communications
></tui-tooltip> with your server.
</ion-row> <br />
<div class="ion-text-center"> <ion-button
<h2>Trust Root CA</h2> strong
<p>Follow instructions for your OS</p> size="small"
</div> shape="round"
<ion-button color="primary"
class="wiz-card-button" href="https://docs.start9.com/0.3.5.x/user-manual/trust-ca#establishing-trust"
shape="round" target="_blank"
(click)="instructions()" noreferrer
[disabled]="!downloadClicked" >
> View Instructions
View Docs <ion-icon slot="end" name="open-outline"></ion-icon>
<ion-icon slot="end" name="open-outline"></ion-icon> </ion-button>
</ion-button> </li>
</div> <li>
</ion-col> <b>Test</b>
<ion-col sizeXs="12" sizeLg="4"> - Refresh the page. If refreshing the page does not work, you may need
<div class="wiz-card" [class.disabled]="!polling && !caTrusted"> to quit and re-open your browser, then revisit this page.
<b class="wiz-step">3</b> <br />
<div class="ion-text-center"> <ion-button
<h2>Go To Login</h2> strong
<p *ngIf="instructionsClicked; else space" class="inline-center"> size="small"
<ion-spinner shape="round"
class="wiz-spinner" class="refresh"
*ngIf="!caTrusted; else trusted" (click)="refresh()"
></ion-spinner> >
<ng-template #trusted> Refresh
<ion-icon name="ribbon-outline" color="success"></ion-icon> <ion-icon slot="end" name="refresh"></ion-icon>
</ng-template> </ion-button>
&nbsp;{{ caTrusted ? 'Root CA trusted!' : 'Waiting for trust...' }} </li>
</p> </ol>
<ng-template #space>
<!-- to keep alignment -->
<p><br /></p>
</ng-template>
</div>
<ion-button
class="wiz-card-button"
shape="round"
(click)="launchHttps()"
[disabled]="!caTrusted"
>
Open
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-button>
</div>
</ion-col>
</ion-row>
<ion-row>
<ion-col class="ion-text-center">
<ion-button fill="clear" (click)="launchHttps()" [disabled]="caTrusted"> <ion-button fill="clear" (click)="launchHttps()" [disabled]="caTrusted">
Skip Skip
<ion-icon slot="end" name="open-outline"></ion-icon> <ion-icon slot="end" name="open-outline"></ion-icon>
</ion-button> </ion-button>
</ion-col> <span class="skip_detail">(not recommended)</span>
</ion-row> </ion-card>
</ion-grid> </ng-container>
<ng-template #trusted>
<ion-card id="trusted" class="text-center">
<ion-icon
name="shield-checkmark-outline"
class="wiz-icon"
color="success"
></ion-icon>
<h1>Root CA Trusted!</h1>
<p>
You have successfully trusted your server's Root CA and may now log in
securely.
</p>
<ion-button strong (click)="launchHttps()" color="tertiary" shape="round">
Go to login
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-button>
</ion-card>
</ng-template>
</div>
<a <a
id="install-cert" id="install-cert"
href="/eos/local.crt" href="/eos/local.crt"

View File

@@ -1,44 +1,83 @@
.grid-wiz { #trusted {
--ion-grid-padding: 36px; max-width: 40%;
height: 100%
} }
.wiz-icon { #untrusted {
font-size: 84px; max-width: 50%;
} }
.wiz-card { .center-container {
padding: 1rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
}
ion-card {
color: var(--ion-color-dark);
background: #414141; background: #414141;
margin: 24px; box-shadow: 0 4px 4px rgba(17, 17, 17, 0.144);
padding: 16px; border-radius: 35px;
height: 280px; padding: 1.5rem;
border-radius: 16px; width: 100%;
display: grid;
& h2 { h1 {
font-weight: 600; font-weight: bold;
font-size: 1.5rem;
padding-bottom: 1.5rem;
}
p {
font-size: 21px;
line-height: 25px;
margin-bottom: 30px;
margin-top: 0;
} }
} }
.wiz-card-button { .text-center {
justify-self: center; text-align: center;
white-space: normal;
} }
.wiz-spinner { ol {
width: 14px; font-size: 17px;
height: 14px; line-height: 25px;
text-align: left;
li {
padding-bottom: 24px;
}
ion-button {
margin-top: 10px;
}
} }
.disabled { .refresh {
filter: saturate(0.2) contrast(0.5) --background: var(--ion-color-success-shade);
} }
.wiz-step { .wiz-icon {
margin-top: 4px; font-size: 64px;
} }
.inline-center { .skip_detail {
display: inline-flex; display: block;
align-items: center; font-size: 0.8rem;
margin-top: -13px;
padding-bottom: 0.5rem;
}
@media (max-width: 700px) {
#trusted, #untrusted {
max-width: 100%;
}
}
@media (min-width: 701px) and (max-width: 1200px) {
#trusted, #untrusted {
max-width: 75%;
}
} }

View File

@@ -1,7 +1,7 @@
import { Component, Inject } from '@angular/core' import { Component, Inject } from '@angular/core'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'
import { pauseFor, RELATIVE_URL } from '@start9labs/shared' import { RELATIVE_URL } from '@start9labs/shared'
import { DOCUMENT } from '@angular/common' import { DOCUMENT } from '@angular/common'
import { WINDOW } from '@ng-web-apis/common' import { WINDOW } from '@ng-web-apis/common'
@@ -11,9 +11,6 @@ import { WINDOW } from '@ng-web-apis/common'
styleUrls: ['./ca-wizard.component.scss'], styleUrls: ['./ca-wizard.component.scss'],
}) })
export class CAWizardComponent { export class CAWizardComponent {
downloadClicked = false
instructionsClicked = false
polling = false
caTrusted = false caTrusted = false
constructor( constructor(
@@ -25,51 +22,27 @@ export class CAWizardComponent {
) {} ) {}
async ngOnInit() { async ngOnInit() {
if (!this.config.isSecure()) { await this.testHttps().catch(e =>
await this.testHttps().catch(e => console.warn('Failed Https connection attempt'),
console.warn('Failed Https connection attempt'), )
)
}
} }
download() { download() {
this.downloadClicked = true
this.document.getElementById('install-cert')?.click() this.document.getElementById('install-cert')?.click()
} }
instructions() { refresh() {
this.windowRef.open( this.document.location.reload()
'https://docs.start9.com/0.3.5.x/getting-started/trust-ca/#trust-root-ca',
'_blank',
'noreferrer',
)
this.instructionsClicked = true
this.startDaemon()
}
private async startDaemon(): Promise<void> {
this.polling = true
while (this.polling) {
try {
await this.testHttps()
this.polling = false
} catch (e) {
console.warn('Failed Https connection attempt')
await pauseFor(2000)
}
}
} }
launchHttps() { launchHttps() {
const host = this.config.getHost() const host = this.config.getHost()
this.windowRef.open(`https://${host}`, '_blank', 'noreferrer') this.windowRef.open(`https://${host}`, '_self')
} }
private async testHttps() { private async testHttps() {
const url = `https://${this.document.location.host}${this.relativeUrl}` const url = `https://${this.document.location.host}${this.relativeUrl}`
await this.api.echo({ message: 'ping' }, url).then(() => { await this.api.echo({ message: 'ping' }, url).then(() => {
this.downloadClicked = true
this.instructionsClicked = true
this.caTrusted = true this.caTrusted = true
}) })
} }

View File

@@ -11,7 +11,17 @@
<ion-icon slot="start" name="warning-outline"></ion-icon> <ion-icon slot="start" name="warning-outline"></ion-icon>
<ion-label> <ion-label>
<h2 style="font-weight: bold">Http detected</h2> <h2 style="font-weight: bold">Http detected</h2>
<p style="font-weight: 600">Tor is faster over https.</p> <p style="font-weight: 600">
Tor is faster over https. Your Root CA must be trusted.
<a
href="https://docs.start9.com/0.3.5.x/user-manual/trust-ca"
target="_blank"
noreferrer
style="color: black"
>
View instructions
</a>
</p>
</ion-label> </ion-label>
<ion-button slot="end" color="light" (click)="launchHttps()"> <ion-button slot="end" color="light" (click)="launchHttps()">
Open Https Open Https
@@ -48,7 +58,6 @@
[type]="unmasked ? 'text' : 'password'" [type]="unmasked ? 'text' : 'password'"
[(ngModel)]="password" [(ngModel)]="password"
(ionChange)="error = ''" (ionChange)="error = ''"
maxlength="64"
></ion-input> ></ion-input>
<ion-button <ion-button
slot="end" slot="end"

View File

@@ -29,7 +29,7 @@ export class LoginPage {
launchHttps() { launchHttps() {
const host = this.config.getHost() const host = this.config.getHost()
this.windowRef.open(`https://${host}`, '_blank', 'noreferrer') this.windowRef.open(`https://${host}`, '_self')
} }
async submit() { async submit() {

View File

@@ -1,3 +1,4 @@
.metric-note { ion-note {
font-size: 16px; font-size: 16px;
color: white;
} }

View File

@@ -83,7 +83,7 @@ export class AppShowStatusComponent {
PrimaryStatus.Running, PrimaryStatus.Running,
PrimaryStatus.Starting, PrimaryStatus.Starting,
PrimaryStatus.Restarting, PrimaryStatus.Restarting,
].includes(this.status.primary) ].includes(this.status.primary as PrimaryStatus)
} }
get isStopped(): boolean { get isStopped(): boolean {

View File

@@ -6,18 +6,25 @@
[style.font-style]="style" [style.font-style]="style"
[style.font-weight]="weight" [style.font-weight]="weight"
> >
<span *ngIf="!installProgress"> {{ (connected$ | async) ? rendering.display : 'Unknown' }}
{{ (connected$ | async) ? rendering.display : 'Unknown' }}
<span *ngIf="rendering.showDots" class="loading-dots"></span> <span
*ngIf="
rendering.display === PR[PS.Stopping].display &&
(sigtermTimeout | durationToSeconds) > 30
"
>
this may take a while
</span> </span>
<span *ngIf="installProgress"> <span *ngIf="installProgress">
<ion-text <ion-text
*ngIf="installProgress | installProgressDisplay as progress" *ngIf="installProgress | installProgressDisplay as progress"
color="primary" color="primary"
> >
Installing
<span class="loading-dots"></span>
{{ progress }} {{ progress }}
</ion-text> </ion-text>
</span> </span>
<span *ngIf="rendering.showDots" class="loading-dots"></span>
</p> </p>

View File

@@ -15,7 +15,7 @@
<h2> <h2>
For a secure local connection and faster Tor experience, For a secure local connection and faster Tor experience,
<a <a
href="https://docs.start9.com/0.3.5.x/getting-started/connecting-lan" href="https://docs.start9.com/0.3.5.x/user-manual/connecting-lan"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >

View File

@@ -1,7 +1,7 @@
<logs <logs
[fetchLogs]="fetchLogs()" [fetchLogs]="fetchLogs()"
[followLogs]="followLogs()" [followLogs]="followLogs()"
context="eos" context="start-os"
defaultBack="system" defaultBack="system"
pageTitle="OS Logs" pageTitle="OS Logs"
class="ion-page" class="ion-page"

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { Metrics } from 'src/app/services/api/api.types' import { Metrics } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { TimeInfo, TimeService } from 'src/app/services/time-service' import { TimeService } from 'src/app/services/time-service'
import { import {
catchError, catchError,
combineLatest, combineLatest,
@@ -29,9 +29,24 @@ export class ServerMetricsPage {
private readonly connectionService: ConnectionService, private readonly connectionService: ConnectionService,
) {} ) {}
private getServerData$(): Observable<[TimeInfo, Metrics]> { private getServerData$(): Observable<
[
{
value: number
synced: boolean
},
{
days: number
hours: number
minutes: number
seconds: number
},
Metrics,
]
> {
return combineLatest([ return combineLatest([
this.timeService.getTimeInfo$(), this.timeService.now$,
this.timeService.uptime$,
this.getMetrics$(), this.getMetrics$(),
]).pipe( ]).pipe(
catchError(() => { catchError(() => {

Some files were not shown because too many files have changed in this diff Show More