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
- unstable
- dev-unstable
- podman
- dev-podman
- dev-unstable-podman
- docker
- dev-docker
- dev-unstable-docker
runner:
type: choice
description: Runner
@@ -31,6 +31,13 @@ on:
- aarch64
- aarch64-nonfree
- raspberrypi
deploy:
type: choice
description: Deploy
options:
- NONE
- alpha
- beta
push:
branches:
- master
@@ -42,11 +49,55 @@ on:
env:
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:
all:
name: Build
compile:
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:
fail-fast: false
matrix:
@@ -68,86 +119,30 @@ jobs:
format(
'["ubuntu-22.04", "{0}"]',
fromJson('{
"x86_64": ["buildjet-32vcpu-ubuntu-2204", "buildjet-32vcpu-ubuntu-2204"],
"x86_64-nonfree": ["buildjet-32vcpu-ubuntu-2204", "buildjet-32vcpu-ubuntu-2204"],
"aarch64": ["buildjet-16vcpu-ubuntu-2204-arm", "buildjet-32vcpu-ubuntu-2204-arm"],
"aarch64-nonfree": ["buildjet-16vcpu-ubuntu-2204-arm", "buildjet-32vcpu-ubuntu-2204-arm"],
"raspberrypi": ["buildjet-16vcpu-ubuntu-2204-arm", "buildjet-32vcpu-ubuntu-2204-arm"],
}')[matrix.platform][github.event.inputs.platform == matrix.platform]
"x86_64": "buildjet-8vcpu-ubuntu-2204",
"x86_64-nonfree": "buildjet-8vcpu-ubuntu-2204",
"aarch64": "buildjet-8vcpu-ubuntu-2204-arm",
"aarch64-nonfree": "buildjet-8vcpu-ubuntu-2204-arm",
"raspberrypi": "buildjet-8vcpu-ubuntu-2204-arm",
}')[matrix.platform]
)
)[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:
- 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
with:
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
run: |
@@ -166,52 +161,77 @@ jobs:
- run: sudo mount -t tmpfs tmpfs /var/tmp/debspawn
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:
path: /var/lib/debspawn
key: ${{ runner.os }}-${{ matrix.platform }}-debspawn-init
name: compiled-${{ env.ARCH }}.tar
- 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/"
- run: "sudo rm -rf embassy-os-deb ${{ steps.npm-cache-dir.outputs.dir }} $HOME/.cargo"
- name: Prevent rebuild of compiled artifacts
run: |
mkdir -p frontend/dist/raw
PLATFORM=${{ matrix.platform }} make -t compiled-${{ env.ARCH }}.tar
- name: Run iso build
working-directory: startos-image-recipes
run: |
./run-local-build.sh ${{ matrix.platform }}
run: PLATFORM=${{ matrix.platform }} make iso
if: ${{ matrix.platform != 'raspberrypi' }}
- name: Run img build
run: PLATFORM=${{ matrix.platform }} make img
if: ${{ matrix.platform == 'raspberrypi' }}
- uses: actions/upload-artifact@v3
with:
name: ${{ matrix.platform }}.squashfs
path: startos-image-recipes/results/*.squashfs
path: results/*.squashfs
- uses: actions/upload-artifact@v3
with:
name: ${{ matrix.platform }}.iso
path: startos-image-recipes/results/*.iso
path: results/*.iso
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
with:
name: raspberrypi.img
path: start-os/startos-*_raspberrypi.img
name: ${{ matrix.platform }}.img
path: results/*.img
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
.vscode/
/cargo-deps/**/*
/PLATFORM.txt
/ENVIRONMENT.txt
/GIT_HASH.txt
/VERSION.txt
/embassyos-*.tar.gz
/eos-*.tar.gz
/*.deb
/target
/*.squashfs
/debian
/DEBIAN
/results
/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}")
ARCH := $(shell if [ "$(OS_ARCH)" = "raspberrypi" ]; then echo aarch64; else echo $(OS_ARCH) | sed 's/-nonfree$$//g'; fi)
ENVIRONMENT_FILE = $(shell ./check-environment.sh)
GIT_HASH_FILE = $(shell ./check-git-hash.sh)
VERSION_FILE = $(shell ./check-version.sh)
PLATFORM_FILE := $(shell ./check-platform.sh)
ENVIRONMENT_FILE := $(shell ./check-environment.sh)
GIT_HASH_FILE := $(shell ./check-git-hash.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_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)
COMPAT_SRC := $(shell find system-images/compat/ -not -path 'system-images/compat/target/*' -and -not -name *.tar -and -not -name target)
UTILS_SRC := $(shell find system-images/utils/ -not -name *.tar)
BINFMT_SRC := $(shell find system-images/binfmt/ -not -name *.tar)
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
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_UI_SRC := $(shell find frontend/projects/ui)
FRONTEND_SETUP_WIZARD_SRC := $(shell find frontend/projects/setup-wizard)
FRONTEND_DIAGNOSTIC_UI_SRC := $(shell find frontend/projects/diagnostic-ui)
FRONTEND_INSTALL_WIZARD_SRC := $(shell find 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)
COMPAT_SRC := $(shell git ls-files system-images/compat/)
UTILS_SRC := $(shell git ls-files system-images/utils/)
BINFMT_SRC := $(shell git ls-files system-images/binfmt/)
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 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 git ls-files frontend/projects/ui)
FRONTEND_SETUP_WIZARD_SRC := $(shell git ls-files frontend/projects/setup-wizard)
FRONTEND_DIAGNOSTIC_UI_SRC := $(shell git ls-files frontend/projects/diagnostic-ui)
FRONTEND_INSTALL_WIZARD_SRC := $(shell git ls-files frontend/projects/install-wizard)
PATCH_DB_CLIENT_SRC := $(shell git ls-files --recurse-submodules patch-db/client)
GZIP_BIN := $(shell which pigz || which gzip)
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),)
mkdir = mkdir -p $1
@@ -40,14 +46,14 @@ define cp
endef
endif
.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)
metadata: $(VERSION_FILE) $(PLATFORM_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
sudo:
sudo true
@@ -64,9 +70,13 @@ clean:
rm -rf patch-db/client/dist
rm -rf patch-db/target
rm -rf cargo-deps
rm ENVIRONMENT.txt
rm GIT_HASH.txt
rm VERSION.txt
rm -rf dpkg-workdir
rm -rf image-recipe/deb
rm -rf results
rm -f ENVIRONMENT.txt
rm -f PLATFORM.txt
rm -f GIT_HASH.txt
rm -f VERSION.txt
format:
cd backend && cargo +nightly fmt
@@ -75,8 +85,20 @@ format:
sdk:
cd backend/ && ./install-sdk.sh
startos_raspberrypi.img: $(BUILD_SRC) startos.raspberrypi.squashfs $(VERSION_FILE) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) | sudo
./build/raspberrypi/make-image.sh
deb: results/$(BASENAME).deb
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
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/avahi-alias)
$(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
$(call mkdir,$(DESTDIR)/lib/systemd/system)
$(call cp,backend/startd.service,$(DESTDIR)/lib/systemd/system/startd.service)
$(call mkdir,$(DESTDIR)/usr/lib)
$(call rm,$(DESTDIR)/usr/lib/embassy)
$(call cp,build/lib,$(DESTDIR)/usr/lib/embassy)
$(call rm,$(DESTDIR)/usr/lib/startos)
$(call cp,build/lib,$(DESTDIR)/usr/lib/startos)
$(call cp,ENVIRONMENT.txt,$(DESTDIR)/usr/lib/embassy/ENVIRONMENT.txt)
$(call cp,GIT_HASH.txt,$(DESTDIR)/usr/lib/embassy/GIT_HASH.txt)
$(call cp,VERSION.txt,$(DESTDIR)/usr/lib/embassy/VERSION.txt)
$(call cp,PLATFORM.txt,$(DESTDIR)/usr/lib/startos/PLATFORM.txt)
$(call cp,ENVIRONMENT.txt,$(DESTDIR)/usr/lib/startos/ENVIRONMENT.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 cp,libs/target/aarch64-unknown-linux-musl/release/embassy_container_init,$(DESTDIR)/usr/lib/embassy/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 mkdir,$(DESTDIR)/usr/lib/startos/container)
$(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/startos/container/embassy_container_init.amd64)
$(call mkdir,$(DESTDIR)/usr/lib/embassy/system-images)
$(call cp,system-images/compat/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/embassy/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/binfmt/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/embassy/system-images/binfmt.tar)
$(call mkdir,$(DESTDIR)/usr/lib/startos/system-images)
$(call cp,system-images/compat/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/compat.tar)
$(call cp,system-images/utils/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/utils.tar)
$(call cp,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[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m"
@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")
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) OS_ARCH=$(OS_ARCH)
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) PLATFORM=$(PLATFORM)
$(call ssh,"sudo systemctl start startd")
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
$(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)
$(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)"')
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/embassy/next PLATFORM=$(PLATFORM)
$(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
$(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")
system-images/compat/docker-images/aarch64.tar system-images/compat/docker-images/x86_64.tar: $(COMPAT_SRC) backend/Cargo.lock | sudo
cd system-images/compat && make && touch docker-images/*.tar
upload-ota: results/$(BASENAME).squashfs
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
cd system-images/utils && make && touch docker-images/*.tar
build/lib/depends build/lib/conflicts: build/dpkg-deps/*
build/dpkg-deps/generate.sh
system-images/binfmt/docker-images/aarch64.tar system-images/binfmt/docker-images/x86_64.tar: $(BINFMT_SRC) | sudo
cd system-images/binfmt && make && touch docker-images/*.tar
system-images/compat/docker-images/$(ARCH).tar: $(COMPAT_SRC) backend/Cargo.lock
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
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
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
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
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
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
frontend/dist/static: $(EMBASSY_UIS)
frontend/dist/static: $(EMBASSY_UIS) $(ENVIRONMENT_FILE)
./compress-uis.sh
frontend/config.json: $(GIT_HASH_FILE) frontend/config-sample.json
jq '.useMocks = false' frontend/config-sample.json > 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
jq '.useMocks = false' frontend/config-sample.json | jq '.gitHash = "$(shell cat GIT_HASH.txt)"' > frontend/config.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
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
# used by github actions
backend-$(ARCH).tar: $(EMBASSY_BINS)
compiled-$(ARCH).tar: $(COMPILED_TARGETS) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE)
tar -cvf $@ $^
# 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
ui: frontend/dist/raw/ui
# used by github actions
backend: $(EMBASSY_BINS)
cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep: | sudo
cargo-deps/aarch64-unknown-linux-gnu/release/pi-beep:
ARCH=aarch64 ./build-cargo-dep.sh pi-beep
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"
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]]
name = "glob"
version = "0.3.1"
@@ -4970,12 +4948,12 @@ dependencies = [
"digest 0.10.7",
"divrem",
"ed25519 2.2.3",
"ed25519-dalek 1.0.1",
"ed25519-dalek 2.0.0",
"embassy_container_init",
"emver",
"fd-lock-rs",
"futures",
"git-version",
"gpt",
"helpers",
"hex",

View File

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

View File

@@ -3,11 +3,6 @@
set -e
shopt -s expand_aliases
if [ -z "$OS_ARCH" ]; then
>&2 echo '$OS_ARCH is required'
exit 1
fi
if [ -z "$ARCH" ]; then
ARCH=$(uname -m)
fi
@@ -23,27 +18,17 @@ if tty -s; then
fi
cd ..
FLAGS=""
FEATURES="$(echo $ENVIRONMENT | sed 's/-/,/g')"
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-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-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 -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)":/home/rust/src -P messense/rust-musl-cross:$ARCH-musl'
set +e
fail=
echo "FLAGS=\"$FLAGS\""
echo "FEATURES=\"$FEATURES\""
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
fail=true
fi

View File

@@ -11,8 +11,8 @@ fi
frontend="../frontend/dist/static"
[ -d "$frontend" ] || mkdir -p "$frontend"
if [ -z "$OS_ARCH" ]; then
export OS_ARCH=$(uname -m)
if [ -z "$PLATFORM" ]; then
export PLATFORM=$(uname -m)
fi
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 openssl::pkey::{PKey, Private};
use openssl::x509::X509;
@@ -14,7 +15,7 @@ fn hash_password(password: &str) -> Result<String, Error> {
argon2::hash_encoded(
password.as_bytes(),
&rand::random::<[u8; 16]>()[..],
&argon2::Config::default(),
&argon2::Config::rfc9106_low_mem(),
)
.with_kind(crate::ErrorKind::PasswordHashGeneration)
}
@@ -29,11 +30,11 @@ pub struct AccountInfo {
pub root_ca_cert: X509,
}
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 hostname = generate_hostname();
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 {
server_id,
hostname,

View File

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

View File

@@ -189,7 +189,7 @@ pub async fn recover_full_embassy(
os_backup.account.password = argon2::hash_encoded(
embassy_password.as_bytes(),
&rand::random::<[u8; 16]>()[..],
&argon2::Config::default(),
&argon2::Config::rfc9106_low_mem(),
)
.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::{command, run_cli, Context};
use serde_json::Value;
use crate::context::CliContext;
use crate::procedure::js_scripts::ExecuteArgs;
use crate::s9pk::manifest::PackageId;
use crate::util::logger::EmbassyLogger;
use crate::util::serde::{display_serializable, parse_stdin_deserializable};
use crate::version::{Current, VersionT};
use crate::Error;
@@ -16,6 +12,9 @@ lazy_static::lazy_static! {
static ref VERSION_STRING: String = Current::new().semver().to_string();
}
struct DenoContext;
impl Context for DenoContext {}
#[command(subcommands(execute, sandbox))]
fn deno_api() -> Result<(), Error> {
Ok(())
@@ -70,13 +69,11 @@ impl PackageLogger {
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let filter_layer = EnvFilter::builder()
.with_default_directive(
format!("{}=info", std::module_path!().split("::").next().unwrap())
.parse()
.unwrap(),
)
.from_env_lossy();
let filter_layer = EnvFilter::default().add_directive(
format!("{}=warn", std::module_path!().split("::").next().unwrap())
.parse()
.unwrap(),
);
let fmt_layer = fmt::layer().with_writer(std::io::stderr).with_target(true);
let journald_layer = tracing_journald::layer()
.unwrap()
@@ -103,16 +100,8 @@ fn inner_main() -> Result<(), Error> {
command: deno_api,
app: app => app
.name("StartOS Deno Executor")
.version(&**VERSION_STRING)
.arg(
clap::Arg::with_name("config")
.short('c')
.long("config")
.takes_value(true),
),
context: matches => {
CliContext::init(matches)?
},
.version(&**VERSION_STRING),
context: _m => DenoContext,
exit: |e: RpcError| {
match e.data {
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::sound::CHIME;
use crate::util::Invoke;
use crate::{Error, ErrorKind, ResultExt, OS_ARCH};
use crate::{Error, ErrorKind, ResultExt, PLATFORM};
#[instrument(skip_all)]
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")
.arg("-sf")
.arg("/usr/lib/embassy/scripts/fake-apt")
.arg("/usr/lib/startos/scripts/fake-apt")
.arg("/usr/local/bin/apt")
.invoke(crate::ErrorKind::OpenSsh)
.await?;
Command::new("ln")
.arg("-sf")
.arg("/usr/lib/embassy/scripts/fake-apt")
.arg("/usr/lib/startos/scripts/fake-apt")
.arg("/usr/local/bin/apt-get")
.invoke(crate::ErrorKind::OpenSsh)
.await?;
Command::new("ln")
.arg("-sf")
.arg("/usr/lib/embassy/scripts/fake-apt")
.arg("/usr/lib/startos/scripts/fake-apt")
.arg("/usr/local/bin/aptitude")
.invoke(crate::ErrorKind::OpenSsh)
.await?;
@@ -177,7 +177,7 @@ async fn run_script_if_exists<P: AsRef<Path>>(path: P) {
#[instrument(skip_all)]
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?;
Command::new("sync").invoke(ErrorKind::Filesystem).await?;
crate::sound::SHUTDOWN.play().await?;

View File

@@ -14,9 +14,8 @@ use rpc_toolkit::command;
use tracing::instrument;
use crate::context::RpcContext;
use crate::prelude::*;
use crate::s9pk::manifest::{PackageId};
use crate::s9pk::manifest::PackageId;
use crate::util::display_none;
use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat};
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::fmt;
use std::fmt::Debug;
@@ -105,7 +105,7 @@ where
rng: &mut R,
timeout: &Option<Duration>,
) -> 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::PgPool;
use tokio::sync::{broadcast, oneshot, Mutex, RwLock};
use tokio::time::Instant;
use tracing::instrument;
use super::setup::CURRENT_SECRET;
@@ -29,7 +30,7 @@ use crate::install::cleanup::{cleanup_failed, uninstall};
use crate::manager::ManagerMap;
use crate::middleware::auth::HashSessionToken;
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::notifications::NotificationManager;
use crate::shutdown::Shutdown;
@@ -123,6 +124,7 @@ pub struct RpcContextSeed {
pub current_secret: Arc<Jwk>,
pub client: Client,
pub hardware: Hardware,
pub start_time: Instant,
}
pub struct Hardware {
@@ -158,7 +160,7 @@ impl RpcContext {
base.dns_bind
.as_deref()
.unwrap_or(&[SocketAddr::from(([127, 0, 0, 1], 53))]),
SslManager::new(&account)?,
SslManager::new(&account, root_ca_start_time().await?)?,
&account.hostname,
&account.key,
)
@@ -214,6 +216,7 @@ impl RpcContext {
.build()
.with_kind(crate::ErrorKind::ParseUrl)?,
hardware: Hardware { devices, ram },
start_time: Instant::now(),
});
let res = Self(seed.clone());

View File

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

View File

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

View File

@@ -2,13 +2,12 @@ use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use async_trait::async_trait;
use color_eyre::eyre::eyre;
use digest::generic_array::GenericArray;
use digest::{Digest, OutputSizeUser};
use sha2::Sha256;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use super::{FileSystem, MountType};
use crate::util::Invoke;
use crate::{Error, ResultExt};
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,
) -> Result<(), Error> {
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("ecryptfs")
.arg(src.as_ref())
@@ -25,22 +24,9 @@ pub async fn mount_ecryptfs<P0: AsRef<Path>, P1: AsRef<Path>>(
.arg("-o")
// 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))
.stdin(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()?;
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(())
}
.input(Some(&mut std::io::Cursor::new(b"\n")))
.invoke(crate::ErrorKind::Filesystem).await?;
Ok(())
}
pub struct EcryptFS<EncryptedDir: AsRef<Path>, Key: AsRef<str>> {

View File

@@ -1,9 +1,8 @@
use std::path::Path;
use std::process::Stdio;
use async_compression::tokio::bufread::GzipDecoder;
use tokio::fs::File;
use tokio::io::{AsyncRead, AsyncWriteExt, BufReader};
use tokio::io::{AsyncRead, BufReader};
use tokio::process::Command;
use crate::disk::fsck::RequiresReboot;
@@ -23,7 +22,7 @@ pub async fn update_firmware() -> Result<RequiresReboot, Error> {
if product_name.is_empty() {
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() {
let current_firmware = String::from_utf8(
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?;
while let Some(entry) = firmware_read_dir.next_entry().await? {
let filename = entry.file_name().to_string_lossy().into_owned();
let rdr: Option<Box<dyn AsyncRead + Unpin>> = if filename.ends_with(".rom.gz") {
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 {
None
};
let rdr: Option<Box<dyn AsyncRead + Unpin + Send>> =
if filename.ends_with(".rom.gz") {
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 {
None
};
if let Some(mut rdr) = rdr {
let mut flashrom = Command::new("flashrom")
Command::new("flashrom")
.arg("-p")
.arg("internal")
.arg("-w-")
.stdin(Stdio::piped())
.spawn()?;
let mut rom_dest = flashrom.stdin.take().or_not_found("stdin")?;
tokio::io::copy(&mut rdr, &mut rom_dest).await?;
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));
}
.input(Some(&mut rdr))
.invoke(ErrorKind::Firmware)
.await?;
return Ok(RequiresReboot(true));
}
}
}

View File

@@ -1,7 +1,7 @@
use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::time::Duration;
use std::time::{Duration, SystemTime};
use color_eyre::eyre::eyre;
use helpers::NonDetachingJoinHandle;
@@ -19,7 +19,6 @@ use crate::install::PKG_ARCHIVE_DIR;
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
use crate::prelude::*;
use crate::sound::BEP;
use crate::system::time;
use crate::util::cpupower::{
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?;
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")
.arg("restart")
.arg("systemd-journald")
@@ -263,6 +273,9 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
tracing::info!("Mounted Logs");
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() {
tokio::fs::create_dir_all(&tmp_dir).await?;
}
@@ -275,9 +288,6 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
.datadir()
.join(format!("package-data/tmp/{CONTAINER_TOOL}"));
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" {
Command::new("systemctl")
.arg("stop")
@@ -309,7 +319,7 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
}
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!("Loading Package Docker Images");
@@ -361,15 +371,28 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
}
}
let mut warn_time_not_synced = true;
for _ in 0..60 {
let mut time_not_synced = true;
let mut not_made_progress = 0u32;
for _ in 0..1800 {
if check_time_is_synchronized().await? {
warn_time_not_synced = false;
time_not_synced = false;
break;
}
let t = SystemTime::now();
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");
} else {
tracing::info!("Syncronized system clock");
@@ -383,9 +406,24 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
updated: false,
update_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| {
v.as_server_info_mut().ser(&server_info)?;

View File

@@ -173,7 +173,7 @@ where
);
cleanup(ctx, id, &version).await?;
cleanup_folder(volume_dir, Arc::new(dependents_paths)).await;
remove_tor_keys(secrets, id).await?;
remove_network_keys(secrets, id).await?;
ctx.db
.mutate(|d| {
@@ -188,12 +188,15 @@ where
}
#[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
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)
.execute(secrets)
.execute(&mut *secrets)
.await?;
Ok(())
}

View File

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

View File

@@ -5,12 +5,21 @@ pub const DEFAULT_MARKETPLACE: &str = "https://registry.start9.com";
pub const BUFFER_SIZE: usize = 1024;
pub const HOST_IP: [u8; 4] = [172, 18, 0, 1];
pub const TARGET: &str = current_platform::CURRENT_PLATFORM;
pub const OS_ARCH: &str = env!("OS_ARCH");
lazy_static::lazy_static! {
pub static ref ARCH: &'static str = {
let (arch, _) = TARGET.split_once("-").unwrap();
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;
@@ -56,6 +65,8 @@ pub mod util;
pub mod version;
pub mod volume;
use std::time::SystemTime;
pub use config::Config;
pub use error::{Error, ErrorKind, ResultExt};
use rpc_toolkit::command;

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use futures::FutureExt;
use libc::time_t;
use openssl::asn1::{Asn1Integer, Asn1Time};
use openssl::bn::{BigNum, MsbOption};
use openssl::ec::{EcGroup, EcKey};
@@ -20,12 +21,20 @@ use tracing::instrument;
use crate::account::AccountInfo;
use crate::context::RpcContext;
use crate::hostname::Hostname;
use crate::init::check_time_is_synchronized;
use crate::net::dhcp::ips;
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.
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)]
pub struct CertPair {
pub ed25519: X509,
@@ -55,9 +64,13 @@ impl CertPair {
}),
);
if cert
.not_after()
.compare(Asn1Time::days_from_now(30)?.as_ref())?
== Ordering::Greater
.not_before()
.compare(Asn1Time::days_from_now(0)?.as_ref())?
== Ordering::Less
&& cert
.not_after()
.compare(Asn1Time::days_from_now(30)?.as_ref())?
== Ordering::Greater
&& ips.is_superset(&ip)
{
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)]
pub struct SslManager {
hostname: Hostname,
@@ -89,9 +110,13 @@ pub struct SslManager {
cert_cache: RwLock<BTreeMap<Key, CertPair>>,
}
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_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 {
hostname: account.hostname.clone(),
root_cert: account.root_ca_cert.clone(),
@@ -160,14 +185,20 @@ pub fn generate_key() -> Result<PKey<Private>, Error> {
}
#[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()?;
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)?;
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_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(
signer: (&PKey<Private>, &X509),
applicant: &PKey<Private>,
start_time: SystemTime,
) -> Result<X509, Error> {
let mut builder = X509Builder::new()?;
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)?;
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_serial_number(&*rand_serial()?)?;
@@ -344,17 +378,10 @@ pub fn make_leaf_cert(
let mut builder = X509Builder::new()?;
builder.set_version(CERTIFICATE_VERSION)?;
let embargo = Asn1Time::from_unix(
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,
)?;
let embargo = Asn1Time::from_unix(unix_time(SystemTime::now()) - 86400)?;
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
let expiration = Asn1Time::days_from_now(397)?;
builder.set_not_after(&expiration)?;

View File

@@ -272,7 +272,14 @@ impl VHostServer {
.await
.with_kind(crate::ErrorKind::OpenSsl)?;
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();
tokio::io::copy_bidirectional(
&mut tls_stream,
@@ -287,7 +294,14 @@ impl VHostServer {
cfg.alpn_protocols.push(proto.into());
}
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();
tokio::io::copy_bidirectional(
&mut tls_stream,
@@ -298,7 +312,14 @@ impl VHostServer {
Err(AlpnInfo::Specified(alpn)) => {
cfg.alpn_protocols = alpn;
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();
tokio::io::copy_bidirectional(
&mut tls_stream,
@@ -308,10 +329,12 @@ impl VHostServer {
}
}
.map_or_else(
|e| match e.kind() {
std::io::ErrorKind::UnexpectedEof => Ok(()),
|e| {
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),
},
}},
|_| Ok(()),
)?;
} else {
@@ -327,8 +350,10 @@ impl VHostServer {
});
}
Err(e) => {
tracing::error!("Error in VHostController on port {port}: {e}");
tracing::debug!("{e:?}");
tracing::trace!(
"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::time::Duration;
use async_stream::stream;
use color_eyre::eyre::eyre;
use color_eyre::Report;
use futures::future::{BoxFuture, Either as EitherFuture};
use futures::{FutureExt, TryStreamExt};
use helpers::{NonDetachingJoinHandle, UnixRpcClient};
@@ -396,7 +394,7 @@ impl DockerProcedure {
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) {
cmd.stdin(std::process::Stdio::piped());
Some(format.to_vec(input)?)
@@ -756,7 +754,7 @@ impl DockerProcedure {
+ 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();
if let Some(shm_size_mb) = self.shm_size_mb {
res.push(OsStr::new("--shm-size").into());
@@ -769,7 +767,7 @@ impl DockerProcedure {
res.extend(self.args.iter().map(|s| OsStr::new(s).into()));
Ok(res)
res
}
}
@@ -813,7 +811,7 @@ impl LongRunning {
socket_path: &Path,
) -> Result<tokio::process::Command, Error> {
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");
remove_container(container_name, true).await?;
@@ -892,23 +890,12 @@ async fn buf_reader_to_lines(
reader: impl AsyncBufRead + Unpin,
limit: impl Into<Option<usize>>,
) -> Result<Vec<String>, Error> {
let lines = stream! {
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await? {
yield Ok::<_, Report>(line);
}
};
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();
let mut lines = reader.lines();
let mut answer = RingVec::new(limit.into().unwrap_or(1000));
while let Some(line) = lines.next_line().await? {
answer.push(line);
}
let output: Vec<String> = answer.value.into_iter().collect();
Ok(output)
}
@@ -973,4 +960,11 @@ mod tests {
assert_eq!(CAPACITY_IN, ring.value.capacity());
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::time::Duration;
use std::{
path::{Path, PathBuf},
process::Stdio,
};
use color_eyre::eyre::eyre;
use embassy_container_init::ProcessGroupId;
use helpers::UnixRpcClient;
pub use js_engine::JsError;
@@ -19,8 +15,8 @@ use tracing::instrument;
use super::ProcedureName;
use crate::prelude::*;
use crate::s9pk::manifest::PackageId;
use crate::util::io::to_json_async_writer;
use crate::util::Version;
use crate::util::serde::IoFormat;
use crate::util::{Invoke, Version};
use crate::volume::Volumes;
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -85,45 +81,23 @@ impl JsProcedure {
_gid: ProcessGroupId,
_rpc_client: Option<Arc<UnixRpcClient>>,
) -> Result<Result<O, (i32, String)>, Error> {
let runner_argument = ExecuteArgs {
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")
Command::new("start-deno")
.arg("execute")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.kill_on_drop(true)
.spawn()?;
to_json_async_writer(
&mut runner.stdin.take().or_not_found("stdin")?,
&runner_argument,
)
.await?;
let res = if let Some(timeout) = timeout {
tokio::time::timeout(timeout, runner.wait_with_output())
.await
.with_kind(ErrorKind::Timeout)??
} 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,
))
}
.input(Some(&mut std::io::Cursor::new(IoFormat::Json.to_vec(
&ExecuteArgs {
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()),
},
)?)))
.timeout(timeout)
.invoke(ErrorKind::Javascript)
.await
.and_then(|res| IoFormat::Json.from_slice(&res))
}
#[instrument(skip_all)]
@@ -137,45 +111,23 @@ impl JsProcedure {
timeout: Option<Duration>,
name: ProcedureName,
) -> Result<Result<O, (i32, String)>, Error> {
let runner_argument = ExecuteArgs {
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")
Command::new("start-deno")
.arg("sandbox")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.kill_on_drop(true)
.spawn()?;
to_json_async_writer(
&mut runner.stdin.take().or_not_found("stdin")?,
&runner_argument,
)
.await?;
let res = if let Some(timeout) = timeout {
tokio::time::timeout(timeout, runner.wait_with_output())
.await
.with_kind(ErrorKind::Timeout)??
} 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,
))
}
.input(Some(&mut std::io::Cursor::new(IoFormat::Json.to_vec(
&ExecuteArgs {
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()),
},
)?)))
.timeout(timeout)
.invoke(ErrorKind::Javascript)
.await
.and_then(|res| IoFormat::Json.from_slice(&res))
}
#[instrument(skip_all)]

View File

@@ -172,7 +172,13 @@ impl<'de> Deserialize<'de> for NoOutput {
where
D: serde::Deserializer<'de>,
{
let _ = Value::deserialize(deserializer)?;
let _ = Value::deserialize(deserializer);
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",
&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.ram", &ctx.hardware.ram.to_string());

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
use std::fmt;
use chrono::Utc;
use clap::ArgMatches;
use color_eyre::eyre::eyre;
use futures::FutureExt;
use rpc_toolkit::command;
@@ -84,9 +85,65 @@ pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(),
Ok(())
}
#[command]
pub async fn time() -> Result<String, Error> {
Ok(Utc::now().to_rfc3339())
#[derive(Serialize, Deserialize)]
pub struct TimeInfo {
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(
@@ -303,60 +360,44 @@ impl<'de> Deserialize<'de> for GigaBytes {
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsGeneral {
#[serde(rename = "Temperature")]
temperature: Option<Celsius>,
pub temperature: Option<Celsius>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsMemory {
#[serde(rename = "Percentage Used")]
pub percentage_used: Percentage,
#[serde(rename = "Total")]
pub total: MebiBytes,
#[serde(rename = "Available")]
pub available: MebiBytes,
#[serde(rename = "Used")]
pub used: MebiBytes,
#[serde(rename = "Swap Total")]
pub swap_total: MebiBytes,
#[serde(rename = "Swap Free")]
pub swap_free: MebiBytes,
#[serde(rename = "Swap Used")]
pub swap_used: MebiBytes,
pub zram_total: MebiBytes,
pub zram_available: MebiBytes,
pub zram_used: MebiBytes,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsCpu {
#[serde(rename = "User Space")]
user_space: Percentage,
#[serde(rename = "Kernel Space")]
kernel_space: Percentage,
#[serde(rename = "I/O Wait")]
wait: Percentage,
#[serde(rename = "Idle")]
percentage_used: Percentage,
idle: Percentage,
#[serde(rename = "Usage")]
usage: Percentage,
user_space: Percentage,
kernel_space: Percentage,
wait: Percentage,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MetricsDisk {
#[serde(rename = "Size")]
size: GigaBytes,
#[serde(rename = "Used")]
percentage_used: Percentage,
used: GigaBytes,
#[serde(rename = "Available")]
available: GigaBytes,
#[serde(rename = "Percentage Used")]
used_percentage: Percentage,
capacity: GigaBytes,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Metrics {
#[serde(rename = "General")]
general: MetricsGeneral,
#[serde(rename = "Memory")]
memory: MetricsMemory,
#[serde(rename = "CPU")]
cpu: MetricsCpu,
#[serde(rename = "Disk")]
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),
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),
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;
Ok(res)
@@ -695,8 +736,8 @@ pub struct MemInfo {
buffers: Option<u64>,
cached: Option<u64>,
slab: Option<u64>,
swap_total: Option<u64>,
swap_free: Option<u64>,
zram_total: Option<u64>,
zram_free: Option<u64>,
}
#[instrument(skip_all)]
pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
@@ -708,8 +749,8 @@ pub async fn get_mem_info() -> Result<MetricsMemory, Error> {
buffers: None,
cached: None,
slab: None,
swap_total: None,
swap_free: None,
zram_total: None,
zram_free: None,
};
fn get_num_kb(l: &str) -> Result<u64, Error> {
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("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("SwapTotal") => mem_info.swap_total = Some(get_num_kb(entry)?),
_ if entry.starts_with("SwapFree") => mem_info.swap_free = 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.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 cached = ensure_present(mem_info.cached, "Cached")?;
let slab = ensure_present(mem_info.slab, "Slab")?;
let swap_total_k = ensure_present(mem_info.swap_total, "SwapTotal")?;
let swap_free_k = ensure_present(mem_info.swap_free, "SwapFree")?;
let zram_total_k = ensure_present(mem_info.zram_total, "SwapTotal")?;
let zram_free_k = ensure_present(mem_info.zram_free, "SwapFree")?;
let total = MebiBytes(mem_total 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 swap_total = MebiBytes(swap_total_k as f64 / 1024.0);
let swap_free = MebiBytes(swap_free_k as f64 / 1024.0);
let swap_used = MebiBytes((swap_total_k - swap_free_k) as f64 / 1024.0);
let zram_total = MebiBytes(zram_total_k as f64 / 1024.0);
let zram_available = MebiBytes(zram_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);
Ok(MetricsMemory {
percentage_used,
total,
available,
used,
swap_total,
swap_free,
swap_used,
zram_total,
zram_available,
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;
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),
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::util::Invoke;
use crate::{Error, ErrorKind, ResultExt, OS_ARCH};
use crate::{Error, ErrorKind, ResultExt, PLATFORM};
mod latest_information;
@@ -231,7 +231,7 @@ impl EosUrl {
.host_str()
.ok_or_else(|| Error::new(eyre!("Could not get host of base"), ErrorKind::ParseUrl))?;
let version: &Version = &self.version;
Ok(format!("{host}::{version}/{OS_ARCH}/")
Ok(format!("{host}::{version}/{}/", &*PLATFORM)
.parse()
.map_err(|_| Error::new(eyre!("Could not parse path"), ErrorKind::ParseUrl))?)
}
@@ -297,7 +297,7 @@ async fn sync_boot() -> Result<(), Error> {
.await?
.wait()
.await?;
if OS_ARCH != "raspberrypi" {
if &*PLATFORM != "raspberrypi" {
let dev_mnt =
MountGuard::mount(&Bind::new("/dev"), "/media/embassy/next/dev", ReadWrite).await?;
let sys_mnt =

View File

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

View File

@@ -7,14 +7,14 @@ use tokio::process::Command;
use crate::util::Invoke;
#[cfg(not(feature = "podman"))]
#[cfg(feature = "docker")]
pub const CONTAINER_TOOL: &str = "docker";
#[cfg(feature = "podman")]
#[cfg(not(feature = "docker"))]
pub const CONTAINER_TOOL: &str = "podman";
#[cfg(not(feature = "podman"))]
#[cfg(feature = "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 struct DockerImageSha(String);

View File

@@ -26,13 +26,13 @@ use crate::shutdown::Shutdown;
use crate::{Error, ErrorKind, ResultExt as _};
pub mod config;
pub mod cpupower;
pub mod crypto;
pub mod docker;
pub mod http_reader;
pub mod io;
pub mod logger;
pub mod lshw;
pub mod serde;
pub mod crypto;
#[derive(Clone, Copy, Debug, ::serde::Deserialize, ::serde::Serialize)]
pub enum Never {}
@@ -50,30 +50,113 @@ impl std::fmt::Display for Never {
impl std::error::Error for Never {}
#[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_timeout(
&mut self,
error_kind: crate::ErrorKind,
timeout: Option<Duration>,
) -> Result<Vec<u8>, Error>;
}
#[async_trait::async_trait]
impl Invoke for tokio::process::Command {
async fn invoke(&mut self, error_kind: crate::ErrorKind) -> Result<Vec<u8>, Error> {
self.invoke_timeout(error_kind, None).await
pub struct ExtendedCommand<'a> {
cmd: &'a mut tokio::process::Command,
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,
error_kind: crate::ErrorKind,
timeout: Option<Duration>,
) -> Result<Vec<u8>, Error> {
self.kill_on_drop(true);
self.stdout(Stdio::piped());
self.stderr(Stdio::piped());
let res = match timeout {
None => self.output().await?,
Some(t) => tokio::time::timeout(t, self.output())
}
impl<'a> std::ops::DerefMut for ExtendedCommand<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.cmd
}
}
#[async_trait::async_trait]
impl<'a> Invoke<'a> for tokio::process::Command {
type Extended<'ext> = ExtendedCommand<'ext>
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
.with_kind(ErrorKind::Timeout)??,
};

View File

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

View File

@@ -27,6 +27,12 @@ impl Volumes {
volume
.validate(interfaces)
.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(())
}
@@ -131,7 +137,6 @@ pub enum Volume {
#[serde(rename_all = "kebab-case")]
Certificate { interface_id: InterfaceId },
#[serde(rename_all = "kebab-case")]
#[serde(skip)]
Backup { readonly: bool },
}
impl Volume {

View File

@@ -855,7 +855,7 @@ export const action = {
},
/**
* 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
* not created yet. Found this out during the migrations, where the parent would die.
* @param {*} effects
@@ -931,7 +931,7 @@ export const action = {
},
/**
* 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
* @param {*} effects
* @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)
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

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
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
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
firewalld
nginx
nginx-common
nginx-common
openresolv

View File

@@ -6,21 +6,15 @@ bmon
btrfs-progs
ca-certificates
cifs-utils
containerd.io
cryptsetup
curl
dmidecode
docker-ce
docker-ce-cli
docker-compose-plugin
dosfstools
e2fsprogs
ecryptfs-utils
exfatprogs
flashrom
gdb
grub-common
heaptrack
htop
httpdirfs
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 "Welcome to\n"
cat << "ASCII"
╭ ━ ━ ━ ╮ ╭ ╮ ╭ ╮ ╭ ━ ━ ━ ┳ ━ ━ ━ ╮
┃ ╭ ━ ╮ ┣ ╯ ╰ ╮ ╭ ╯ ╰ ┫ ╭ ━ ╮ ┃ ╭ ━ ╮ ┃
┃ ╰ ━ ━ ╋ ╮ ╭ ╋ ━ ━ ┳ ┻ ╮ ╭ ┫ ┃ ┃ ┃ ╰ ━ ━ ╮
╰ ━ ━ ╮ ┃ ┃ ┃ ┃ ╭ ╮ ┃ ╭ ┫ ┃ ┃ ┃ ┃ ┣ ━ ━ ╮ ┃
┃ ╰ ━ ╯ ┃ ┃ ╰ ┫ ╭ ╮ ┃ ┃ ┃ ╰ ┫ ╰ ━ ╯ ┃ ╰ ━ ╯ ┃
╰ ━ ━ ━ ╯ ╰ ━ ┻ ╯ ╰ ┻ ╯ ╰ ━ ┻ ━ ━ ━ ┻ ━ ━ ━ ╯
███████
█ █ █
█ █ █ █
█ █ █ █
█ █ █ █
█ █ █ █
█ █
███████
_____ __ ___ __ __
(_ | /\ |__) | / \(_
__) | / \| \ | \__/__)
ASCII
printf " v$(cat /usr/lib/startos/VERSION.txt)\n\n"
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)"
if [ -n "$(cat /usr/lib/embassy/ENVIRONMENT.txt)" ]; then
printf " ~ $(cat /usr/lib/embassy/ENVIRONMENT.txt)\n"
printf " Git Hash: $(cat /usr/lib/startos/GIT_HASH.txt)"
if [ -n "$(cat /usr/lib/startos/ENVIRONMENT.txt)" ]; then
printf " ~ $(cat /usr/lib/startos/ENVIRONMENT.txt)\n"
else
printf "\n"
fi
printf "\n"
printf " * Documentation: https://start9.com\n"
printf " * Documentation: https://docs.start9.com\n"
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"

View File

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

View File

@@ -13,7 +13,7 @@ fi
>&2 echo ' sudo rm /usr/local/bin/apt'
>&2 echo
>&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 '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.'

View File

@@ -14,7 +14,7 @@ while [ -n "$1" ]; do
done
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[@]}
EOF
fi

View File

@@ -60,12 +60,12 @@ sudo mount `partition_for ${OUTPUT_DEVICE} 2` $TMPDIR
sudo mkdir $TMPDIR/boot
sudo mount `partition_for ${OUTPUT_DEVICE} 1` $TMPDIR/boot
sudo unsquashfs -f -d $TMPDIR startos.raspberrypi.squashfs
REAL_GIT_HASH=$(cat $TMPDIR/usr/lib/embassy/GIT_HASH.txt)
REAL_VERSION=$(cat $TMPDIR/usr/lib/embassy/VERSION.txt)
REAL_ENVIRONMENT=$(cat $TMPDIR/usr/lib/embassy/ENVIRONMENT.txt)
sudo sed -i 's| boot=embassy| init=/usr/lib/embassy/scripts/init_resize\.sh|' $TMPDIR/boot/cmdline.txt
REAL_GIT_HASH=$(cat $TMPDIR/usr/lib/startos/GIT_HASH.txt)
REAL_VERSION=$(cat $TMPDIR/usr/lib/startos/VERSION.txt)
REAL_ENVIRONMENT=$(cat $TMPDIR/usr/lib/startos/ENVIRONMENT.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/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
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
# so the embassies can pull them down
date >> /var/log/resyncRsyncRegistry.runlog
cat > /etc/rsyncd.conf << RD
uid = root
gid = root
use chroot = yes
max connections = 50
max connections = 4
pid file = /var/run/rsyncd.pid
exclude = lost+found/
timeout = 900
@@ -27,7 +29,7 @@ do
filename=${dir##*/}
version=$(echo $directory | sed -r 's/.*\///')
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"
@@ -51,4 +53,4 @@ INSERTING
done
echo "Created rsyncd.conf file, restarting service"
systemctl restart rsync
systemctl restart rsync

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
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
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
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
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
echo overlay >> /etc/initramfs-tools/modules
@@ -21,6 +21,7 @@ update-initramfs -u -k all
if [ -f /etc/default/grub ]; then
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
# change timezone
@@ -46,6 +47,7 @@ dns=systemd-resolved
[ifupdown]
managed=true
EOF
$SYSTEMCTL enable startd.service
$SYSTEMCTL enable systemd-resolved.service
$SYSTEMCTL enable systemd-networkd-wait-online.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/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 '/\(^\|#\)Storage=/c\Storage=persistent' /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 '/\(^\|#\)ForwardToSyslog=/c\ForwardToSyslog=no' /etc/systemd/journald.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
{
"storage-driver": "overlay2"
}
EOF
podman network create -d bridge --subnet 172.18.0.1/24 --opt com.docker.network.bridge.name=br-start9 start9
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
mkdir -p /etc/docker
echo '{ "storage-driver": "overlay2" }' > /etc/docker/daemon.json
else
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
# 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
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
@@ -113,9 +115,9 @@ dpkg-reconfigure --frontend noninteractive locales
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
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/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,
"enableWidgets": false,
"packageArch": "aarch64",
"osArch": "raspberrypi",
"ui": {
"api": {
"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/core": "^0.10.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/start-sdk": "0.4.0-rev0.lib0.rc5",
"@taiga-ui/addon-charts": "3.28.0",
"@taiga-ui/cdk": "3.28.0",
"@taiga-ui/core": "3.28.0",
@@ -76,7 +77,6 @@
"patch-db-client": "file: ../../../patch-db/client",
"pbkdf2": "^3.1.2",
"rxjs": "^7.8.1",
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc5",
"swiper": "^8.2.4",
"ts-matches": "^5.2.1",
"tslib": "^2.3.0",
@@ -115,4 +115,4 @@
"pre-commit": "lint-staged --concurrent false"
}
}
}
}

View File

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

View File

@@ -37,7 +37,7 @@
<p>
Download your server's Root CA and
<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"
rel="noreferrer"
style="color: #6866cc; font-weight: bold; text-decoration: none"
@@ -104,7 +104,7 @@
<span style="font-weight: bold">Note:</span>
This address will only work from a Tor-enabled browser.
<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"
rel="noreferrer"
style="color: #6866cc; font-weight: bold; text-decoration: none"

View File

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

View File

@@ -18,19 +18,24 @@ ion-content {
ion-grid {
max-width: 760px;
height: 100%;
}
.grid-center-wrapper {
height: 100%;
width: 100%;
.inline-container {
display: flex;
justify-content: center;
align-items: center;
}
.card-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 1rem;
}
ion-card {
padding: 3rem;
padding: 2.4rem;
h1 {
color: var(--ion-color-success);
@@ -44,14 +49,14 @@ ion-card {
margin-bottom: 2rem;
}
// download info card
ion-card {
max-width: 91%;
min-width: 91%;
min-height: 260px;
width: 80%;
background: #615F5F;
color: var(--ion-text-color);
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
border-radius: 44px;
margin: auto;
text-align: left;
cursor: pointer;
position: relative;
@@ -70,14 +75,6 @@ ion-card {
font-size: 1.3rem;
}
ion-card-content {
padding-bottom: 4rem;
p {
padding: 1rem 0;
}
}
ion-footer {
position: absolute;
bottom: 10px;
@@ -100,19 +97,24 @@ ion-card {
}
}
.container {
display: flex;
justify-content: center;
align-items: center;
}
#exit {
background: var(--color-accent);
height: 100%;
.container p {
font-size: 1.4rem !important;
font-weight: bold;
.login-button {
--background: var(--color-accent);
--padding-bottom: 2.5rem;
--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);
&:hover {
transition-property: transform;
transform: scale(1.05);
transition-delay: 40ms;
}
ion-icon {
@@ -120,40 +122,62 @@ ion-card {
}
}
#launch {
background: var(--alt-blue);
height: 100%;
.container p {
font-size: 1.4rem !important;
font-weight: bold;
}
ion-icon {
font-size: 1.7rem;
}
.launch-button {
--background: var(--alt-blue);
}
#information:after {
#information:after, #launch:after {
content: '';
position: absolute;
left: 0;
top: 80%;
top: 79%;
width: 100%;
height: 100%;
background: var(--color-accent);
}
#launch:after {
background: var(--alt-blue);
}
}
.card-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 2rem;
.mb-12 {
margin-bottom: 3rem;
}
.emphasis-warn {
font-weight: 600;
color: var(--ion-color-warning);
.pb-2 {
padding-bottom: 0.5rem;
}
.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() {
this.ngZone.runOutsideAngular(() => this.initMatrix())
try {
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)
}
setTimeout(() => this.complete(), 1000)
}
download() {
@@ -83,6 +72,21 @@ export class SuccessPage {
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() {
this.ctx = this.canvas.nativeElement.getContext('2d')!
this.canvas.nativeElement.width = window.innerWidth

View File

@@ -218,7 +218,7 @@ ion-toast {
* {
display: inline-block;
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 { DOCUMENT } from '@angular/common'
import { debounce } from '@start9labs/shared'
import { debounce } from '../../util/misc.util'
@Directive({
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/safe-links/safe-links.directive'
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'

View File

@@ -53,9 +53,9 @@ export function getErrorMessage(
} else if (e.code === 0) {
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'
link = 'https://docs.start9.com/0.3.5.x/support/common-issues#request-error'
} else if (!e.message) {
message = 'Unknown Error'
link = 'https://docs.start9.com/latest/support/faq'
} else {
message = e.message
}

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
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 { SplitPaneTracker } from './services/split-pane.service'
import { PatchDataService } from './services/patch-data.service'
@@ -25,6 +25,19 @@ export class AppComponent implements OnDestroy {
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
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(
private readonly titleService: Title,

View File

@@ -11,11 +11,12 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { IonicModule } from '@ionic/angular'
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
import {
MarkdownModule,
DarkThemeModule,
EnterModule,
LightThemeModule,
MarkdownModule,
ResponsiveColModule,
SharedPipesModule,
LightThemeModule,
} from '@start9labs/shared'
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 { FooterModule } from './app/footer/footer.module'
import { MenuModule } from './app/menu/menu.module'
import { EnterModule } from './app/enter/enter.module'
import { APP_PROVIDERS } from './app.providers'
import { PatchDbModule } from './services/patch-db/patch-db.module'
import { ToastContainerModule } from './common/toast-container/toast-container.module'

View File

@@ -1,6 +1,8 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { PatchDB } from 'patch-db-client'
import { combineLatest, map, Observable, startWith } from 'rxjs'
import { ConnectionService } from 'src/app/services/connection.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
@Component({
selector: 'connection-bar',
@@ -19,8 +21,11 @@ export class ConnectionBarComponent {
}> = combineLatest([
this.connectionService.networkConnected$,
this.websocket$.pipe(startWith(false)),
this.patch
.watch$('server-info', 'status-info')
.pipe(startWith({ restarting: false, 'shutting-down': false })),
]).pipe(
map(([network, websocket]) => {
map(([network, websocket, status]) => {
if (!network)
return {
message: 'No Internet',
@@ -35,6 +40,20 @@ export class ConnectionBarComponent {
icon: 'cloud-offline-outline',
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 {
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">
{{ page.title }}
</ion-label>
<ion-icon
*ngIf="page.url === '/system' && (warning$ | async)"
color="warning"
size="small"
name="warning"
></ion-icon>
<ion-icon
*ngIf="page.url === '/system' && (showEOSUpdate$ | async)"
color="success"
size="small"
name="rocket-outline"
name="rocket"
></ion-icon>
<ion-badge
*ngIf="page.url === '/updates' && (updateCount$ | async) as updateCount"

View File

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

View File

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

View File

@@ -1,44 +1,83 @@
.grid-wiz {
--ion-grid-padding: 36px;
height: 100%
#trusted {
max-width: 40%;
}
.wiz-icon {
font-size: 84px;
#untrusted {
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;
margin: 24px;
padding: 16px;
height: 280px;
border-radius: 16px;
display: grid;
box-shadow: 0 4px 4px rgba(17, 17, 17, 0.144);
border-radius: 35px;
padding: 1.5rem;
width: 100%;
& h2 {
font-weight: 600;
h1 {
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 {
justify-self: center;
white-space: normal;
.text-center {
text-align: center;
}
.wiz-spinner {
width: 14px;
height: 14px;
ol {
font-size: 17px;
line-height: 25px;
text-align: left;
li {
padding-bottom: 24px;
}
ion-button {
margin-top: 10px;
}
}
.disabled {
filter: saturate(0.2) contrast(0.5)
.refresh {
--background: var(--ion-color-success-shade);
}
.wiz-step {
margin-top: 4px;
.wiz-icon {
font-size: 64px;
}
.inline-center {
display: inline-flex;
align-items: center;
.skip_detail {
display: block;
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 { ApiService } from 'src/app/services/api/embassy-api.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 { WINDOW } from '@ng-web-apis/common'
@@ -11,9 +11,6 @@ import { WINDOW } from '@ng-web-apis/common'
styleUrls: ['./ca-wizard.component.scss'],
})
export class CAWizardComponent {
downloadClicked = false
instructionsClicked = false
polling = false
caTrusted = false
constructor(
@@ -25,51 +22,27 @@ export class CAWizardComponent {
) {}
async ngOnInit() {
if (!this.config.isSecure()) {
await this.testHttps().catch(e =>
console.warn('Failed Https connection attempt'),
)
}
await this.testHttps().catch(e =>
console.warn('Failed Https connection attempt'),
)
}
download() {
this.downloadClicked = true
this.document.getElementById('install-cert')?.click()
}
instructions() {
this.windowRef.open(
'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)
}
}
refresh() {
this.document.location.reload()
}
launchHttps() {
const host = this.config.getHost()
this.windowRef.open(`https://${host}`, '_blank', 'noreferrer')
this.windowRef.open(`https://${host}`, '_self')
}
private async testHttps() {
const url = `https://${this.document.location.host}${this.relativeUrl}`
await this.api.echo({ message: 'ping' }, url).then(() => {
this.downloadClicked = true
this.instructionsClicked = true
this.caTrusted = true
})
}

View File

@@ -11,7 +11,17 @@
<ion-icon slot="start" name="warning-outline"></ion-icon>
<ion-label>
<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-button slot="end" color="light" (click)="launchHttps()">
Open Https
@@ -48,7 +58,6 @@
[type]="unmasked ? 'text' : 'password'"
[(ngModel)]="password"
(ionChange)="error = ''"
maxlength="64"
></ion-input>
<ion-button
slot="end"

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@
<h2>
For a secure local connection and faster Tor experience,
<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"
rel="noreferrer"
>

View File

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

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core'
import { Metrics } from 'src/app/services/api/api.types'
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 {
catchError,
combineLatest,
@@ -29,9 +29,24 @@ export class ServerMetricsPage {
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([
this.timeService.getTimeInfo$(),
this.timeService.now$,
this.timeService.uptime$,
this.getMetrics$(),
]).pipe(
catchError(() => {

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