mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5f7e15dfb | ||
|
|
7bf7b1e71e | ||
|
|
7b17498722 | ||
|
|
3473633e43 | ||
|
|
f455b8a007 | ||
|
|
daabba12d3 | ||
|
|
61864d082f | ||
|
|
a7cd1e0ce6 | ||
|
|
0dd6d3a500 | ||
|
|
bdb906bf26 | ||
|
|
61da050fe8 | ||
|
|
83fe391796 | ||
|
|
37657fa6ad | ||
|
|
908a945b95 | ||
|
|
36c720227f | ||
|
|
c22c80d3b0 | ||
|
|
15af827cbc | ||
|
|
4a54c7ca87 | ||
|
|
7b8a0eadf3 | ||
|
|
9a01a0df8e | ||
|
|
ea2d77f536 | ||
|
|
e29003539b | ||
|
|
97bdb2dd64 | ||
|
|
40d446ba32 | ||
|
|
5fa743755d | ||
|
|
0f027fefb8 | ||
|
|
56acb3f281 | ||
|
|
5268185604 | ||
|
|
635c3627c9 | ||
|
|
009f7ddf84 | ||
|
|
4526618c32 | ||
|
|
6dfd46197d | ||
|
|
778471d3cc | ||
|
|
bbcf2990f6 | ||
|
|
ac30ab223b | ||
|
|
50e7b479b5 | ||
|
|
1367428499 | ||
|
|
e5de91cbe5 |
4
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -3,14 +3,14 @@ description: Create a report to help us improve EmbassyOS
|
||||
title: '[bug]: '
|
||||
labels: [Bug, Needs Triage]
|
||||
assignees:
|
||||
- dr-bonez
|
||||
- MattDHill
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Prerequisites
|
||||
description: Please confirm you have completed the following.
|
||||
options:
|
||||
- label: I have searched for [existing issues](https://github.com/start9labs/embassy-os/issues) that already report this problem, without success.
|
||||
- label: I have searched for [existing issues](https://github.com/start9labs/embassy-os/issues) that already report this problem.
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -3,14 +3,14 @@ description: Suggest an idea for EmbassyOS
|
||||
title: '[feat]: '
|
||||
labels: [Enhancement]
|
||||
assignees:
|
||||
- dr-bonez
|
||||
- MattDHill
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Prerequisites
|
||||
description: Please confirm you have completed the following.
|
||||
options:
|
||||
- label: I have searched for [existing issues](https://github.com/start9labs/embassy-os/issues) that already suggest this feature, without success.
|
||||
- label: I have searched for [existing issues](https://github.com/start9labs/embassy-os/issues) that already suggest this feature.
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
12
.github/workflows/backend-pr.yaml
vendored
12
.github/workflows/backend-pr.yaml
vendored
@@ -3,12 +3,6 @@ name: Backend PR
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'backend/**'
|
||||
- 'libs/**'
|
||||
|
||||
jobs:
|
||||
libs:
|
||||
@@ -44,7 +38,7 @@ jobs:
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
libs/target/
|
||||
key: ${{ runner.os }}-cargo-libs-${{ matrix.target }}-${{ hashFiles('libs/Cargo.lock') }}
|
||||
|
||||
- name: Build v8 snapshot
|
||||
@@ -89,14 +83,14 @@ jobs:
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
backend/target/
|
||||
key: ${{ runner.os }}-cargo-backend-${{ hashFiles('backend/Cargo.lock') }}
|
||||
|
||||
- name: Build backend
|
||||
run: make backend
|
||||
|
||||
- name: 'Tar files to preserve file permissions'
|
||||
run: tar -cvf backend.tar backend/target/aarch64-unknown-linux-gnu/release/embassy*
|
||||
run: tar -cvf backend.tar ENVIRONMENT.txt GIT_HASH.txt backend/target/aarch64-unknown-linux-gnu/release/embassy*
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
7
.github/workflows/frontend-pr.yaml
vendored
7
.github/workflows/frontend-pr.yaml
vendored
@@ -3,11 +3,6 @@ name: Frontend PR
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'frontend/**'
|
||||
|
||||
jobs:
|
||||
frontend:
|
||||
@@ -38,7 +33,7 @@ jobs:
|
||||
run: make frontends
|
||||
|
||||
- name: 'Tar files to preserve file permissions'
|
||||
run: tar -cvf frontend.tar frontend/dist frontend/config.json
|
||||
run: tar -cvf frontend.tar ENVIRONMENT.txt GIT_HASH.txt frontend/dist frontend/config.json
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
15
.github/workflows/product.yaml
vendored
15
.github/workflows/product.yaml
vendored
@@ -5,6 +5,11 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
|
||||
jobs:
|
||||
compat:
|
||||
@@ -139,4 +144,12 @@ jobs:
|
||||
key: cache-raspios
|
||||
|
||||
- name: Build image
|
||||
run: "make V=1 eos.img --debug"
|
||||
run: "make V=1 NO_KEY=1 eos.img --debug"
|
||||
|
||||
- name: Compress image
|
||||
run: "gzip eos.img"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: image
|
||||
path: eos.img.gz
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,3 +12,6 @@ deploy_web.sh
|
||||
deploy_web.sh
|
||||
secrets.db
|
||||
.vscode/
|
||||
/cargo-deps/**/*
|
||||
ENVIRONMENT.txt
|
||||
GIT_HASH.txt
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "rpc-toolkit"]
|
||||
path = rpc-toolkit
|
||||
url = https://github.com/Start9Labs/rpc-toolkit.git
|
||||
[submodule "patch-db"]
|
||||
path = patch-db
|
||||
url = https://github.com/Start9Labs/patch-db.git
|
||||
|
||||
44
Makefile
44
Makefile
@@ -1,13 +1,17 @@
|
||||
ENVIRONMENT_FILE := $(shell ./check-environment.sh)
|
||||
GIT_HASH_FILE := $(shell ./check-git-hash.sh)
|
||||
EMBASSY_BINS := backend/target/aarch64-unknown-linux-gnu/release/embassyd backend/target/aarch64-unknown-linux-gnu/release/embassy-init backend/target/aarch64-unknown-linux-gnu/release/embassy-cli backend/target/aarch64-unknown-linux-gnu/release/embassy-sdk
|
||||
EMBASSY_UIS := frontend/dist/ui frontend/dist/setup-wizard frontend/dist/diagnostic-ui
|
||||
EMBASSY_SRC := raspios.img product_key.txt $(EMBASSY_BINS) backend/embassyd.service backend/embassy-init.service $(EMBASSY_UIS) $(shell find build)
|
||||
COMPAT_SRC := $(shell find system-images/compat/src)
|
||||
UTILS_SRC := $(shell find system-images/utils/Dockerfile)
|
||||
BACKEND_SRC := $(shell find backend/src) $(shell find patch-db/*/src) $(shell find rpc-toolkit/*/src) backend/Cargo.toml backend/Cargo.lock
|
||||
FRONTEND_SRC := $(shell find frontend/projects) $(shell find frontend/assets)
|
||||
PATCH_DB_CLIENT_SRC = $(shell find patch-db/client -not -path patch-db/client/dist)
|
||||
GIT_REFS := $(shell find .git/refs/heads)
|
||||
TMP_FILE := $(shell mktemp)
|
||||
BACKEND_SRC := $(shell find backend/src) $(shell find patch-db/*/src) backend/Cargo.toml backend/Cargo.lock
|
||||
FRONTEND_SHARED_SRC := $(shell find frontend/projects/shared) $(shell find frontend/assets) $(shell ls -p frontend/ | grep -v / | sed 's/^/frontend\//g') frontend/node_modules frontend/config.json patch-db/client/dist
|
||||
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)
|
||||
PATCH_DB_CLIENT_SRC := $(shell find patch-db/client -not -path patch-db/client/dist)
|
||||
$(shell sudo true)
|
||||
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
@@ -27,11 +31,12 @@ clean:
|
||||
rm -rf frontend/dist
|
||||
rm -rf patch-db/client/node_modules
|
||||
rm -rf patch-db/client/dist
|
||||
sudo rm -rf cargo-deps
|
||||
|
||||
sdk:
|
||||
cd backend/ && ./install-sdk.sh
|
||||
|
||||
eos.img: $(EMBASSY_SRC) system-images/compat/compat.tar system-images/utils/utils.tar
|
||||
eos.img: $(EMBASSY_SRC) system-images/compat/compat.tar system-images/utils/utils.tar cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
||||
! test -f eos.img || rm eos.img
|
||||
if [ "$(NO_KEY)" = "1" ]; then NO_KEY=1 ./build/make-image.sh; else ./build/make-image.sh; fi
|
||||
|
||||
@@ -57,31 +62,42 @@ snapshots: libs/snapshot-creator/Cargo.toml
|
||||
cd libs/ && ./build-v8-snapshot.sh
|
||||
cd libs/ && ./build-arm-v8-snapshot.sh
|
||||
|
||||
$(EMBASSY_BINS): $(BACKEND_SRC)
|
||||
$(EMBASSY_BINS): $(BACKEND_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE)
|
||||
cd backend && ./build-prod.sh
|
||||
touch $(EMBASSY_BINS)
|
||||
|
||||
frontend/node_modules: frontend/package.json
|
||||
npm --prefix frontend ci
|
||||
|
||||
$(EMBASSY_UIS): $(FRONTEND_SRC) frontend/node_modules patch-db/client patch-db/client/dist frontend/config.json
|
||||
npm --prefix frontend run build:all
|
||||
frontend/dist/ui: $(FRONTEND_UI_SRC) $(FRONTEND_SHARED_SRC) $(ENVIRONMENT_FILE)
|
||||
npm --prefix frontend run build:ui
|
||||
|
||||
frontend/config.json: .git/HEAD $(GIT_REFS)
|
||||
frontend/dist/setup-wizard: $(FRONTEND_SETUP_WIZARD_SRC) $(FRONTEND_SHARED_SRC) $(ENVIRONMENT_FILE)
|
||||
npm --prefix frontend run build:setup-wizard
|
||||
|
||||
frontend/dist/diagnostic-ui: $(FRONTEND_DIAGNOSTIC_UI_SRC) $(FRONTEND_SHARED_SRC) $(ENVIRONMENT_FILE)
|
||||
npm --prefix frontend run build:diagnostic-ui
|
||||
|
||||
frontend/config.json: $(GIT_HASH_FILE) frontend/config-sample.json
|
||||
jq '.useMocks = false' frontend/config-sample.json > frontend/config.json
|
||||
npm --prefix frontend run-script build-config
|
||||
|
||||
patch-db/client/node_modules: patch-db/client/package.json
|
||||
npm --prefix patch-db/client install
|
||||
npm --prefix patch-db/client ci
|
||||
|
||||
patch-db/client/dist: $(PATCH_DB_CLIENT_SRC) patch-db/client/node_modules
|
||||
! test -d patch-db/client/dist || rm -rf patch-db/client/dist
|
||||
rm -rf frontend/.angular/cache
|
||||
npm --prefix patch-db/client run build
|
||||
|
||||
# this is a convenience step to build all frontends - it is not referenced elsewhere in this file
|
||||
frontends: frontend/node_modules frontend/config.json $(EMBASSY_UIS)
|
||||
frontends: $(EMBASSY_UIS)
|
||||
|
||||
# this is a convenience step to build the UI
|
||||
ui: frontend/node_modules frontend/config.json frontend/dist/ui
|
||||
ui: frontend/dist/ui
|
||||
|
||||
# this is a convenience step to build the backend
|
||||
backend: $(EMBASSY_BINS)
|
||||
backend: $(EMBASSY_BINS)
|
||||
|
||||
cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast:
|
||||
./build-cargo-dep.sh nc-broadcast
|
||||
822
backend/Cargo.lock
generated
822
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ keywords = [
|
||||
name = "embassy-os"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Start9Labs/embassy-os"
|
||||
version = "0.3.1"
|
||||
version = "0.3.1-rev.1"
|
||||
|
||||
[lib]
|
||||
name = "embassy"
|
||||
@@ -47,103 +47,100 @@ unstable = ["patch-db/unstable"]
|
||||
[dependencies]
|
||||
aes = { version = "0.7.5", features = ["ctr"] }
|
||||
async-stream = "0.3.3"
|
||||
async-trait = "0.1.51"
|
||||
async-trait = "0.1.56"
|
||||
avahi-sys = { git = "https://github.com/Start9Labs/avahi-sys", version = "0.10.0", branch = "feature/dynamic-linking", features = [
|
||||
"dynamic",
|
||||
], optional = true }
|
||||
base32 = "0.4.0"
|
||||
base64 = "0.13.0"
|
||||
base64ct = "1.5.0"
|
||||
base64ct = "1.5.1"
|
||||
basic-cookies = "0.1.4"
|
||||
bollard = "0.11.0"
|
||||
bollard = "0.13.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
clap = "2.33"
|
||||
color-eyre = "0.5"
|
||||
cookie_store = "0.15.0"
|
||||
clap = "3.2.8"
|
||||
color-eyre = "0.6.1"
|
||||
cookie_store = "0.16.1"
|
||||
current_platform = "0.2.0"
|
||||
digest = "0.10.3"
|
||||
digest-old = { package = "digest", version = "0.9.0" }
|
||||
divrem = "1.0.0"
|
||||
ed25519 = { version = "1.5.2", features = ["pkcs8", "pem", "alloc"] }
|
||||
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
|
||||
emver = { version = "0.1.6", features = ["serde"] }
|
||||
fd-lock-rs = "0.1.3"
|
||||
futures = "0.3.17"
|
||||
fd-lock-rs = "0.1.4"
|
||||
futures = "0.3.21"
|
||||
git-version = "0.3.5"
|
||||
helpers = { path = "../libs/helpers" }
|
||||
hex = "0.4.3"
|
||||
hmac = "0.12.1"
|
||||
http = "0.2.5"
|
||||
hyper = "0.14.13"
|
||||
hyper-ws-listener = { git = "https://github.com/Start9Labs/hyper-ws-listener.git", branch = "main" }
|
||||
imbl = "1.0.1"
|
||||
indexmap = { version = "1.8.1", features = ["serde"] }
|
||||
http = "0.2.8"
|
||||
hyper = "0.14.20"
|
||||
hyper-ws-listener = "0.2.0"
|
||||
imbl = "2.0.0"
|
||||
indexmap = { version = "1.9.1", features = ["serde"] }
|
||||
isocountry = "0.3.2"
|
||||
itertools = "0.10.1"
|
||||
itertools = "0.10.3"
|
||||
js_engine = { path = '../libs/js_engine', optional = true }
|
||||
jsonpath_lib = "0.3.0"
|
||||
lazy_static = "1.4"
|
||||
libc = "0.2.103"
|
||||
log = "0.4.14"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.126"
|
||||
log = "0.4.17"
|
||||
models = { version = "*", path = "../libs/models" }
|
||||
nix = "0.23.0"
|
||||
nom = "7.0.0"
|
||||
nix = "0.24.1"
|
||||
nom = "7.1.1"
|
||||
num = "0.4.0"
|
||||
num_enum = "0.5.4"
|
||||
num_enum = "0.5.7"
|
||||
openssh-keys = "0.5.0"
|
||||
openssl = { version = "0.10.36", features = ["vendored"] }
|
||||
openssl = { version = "0.10.41", features = ["vendored"] }
|
||||
patch-db = { version = "*", path = "../patch-db/patch-db", features = [
|
||||
"trace",
|
||||
] }
|
||||
pbkdf2 = "0.11.0"
|
||||
pin-project = "1.0.8"
|
||||
pin-project = "1.0.11"
|
||||
pkcs8 = { version = "0.9.0", features = ["std"] }
|
||||
platforms = "1.1.0"
|
||||
prettytable-rs = "0.8.0"
|
||||
proptest = "1.0.0"
|
||||
proptest-derive = "0.3.0"
|
||||
rand = "0.7.3"
|
||||
regex = "1.5.4"
|
||||
reqwest = { version = "0.11.4", features = ["stream", "json", "socks"] }
|
||||
reqwest_cookie_store = "0.2.0"
|
||||
rpassword = "5.0.1"
|
||||
rpc-toolkit = { version = "*", path = "../rpc-toolkit/rpc-toolkit" }
|
||||
rust-argon2 = "0.8.3"
|
||||
rand = { version = "0.8.5", features = ["std"] }
|
||||
rand-old = { package = "rand", version = "0.7.3" }
|
||||
regex = "1.6.0"
|
||||
reqwest = { version = "0.11.11", features = ["stream", "json", "socks"] }
|
||||
reqwest_cookie_store = "0.3.0"
|
||||
rpassword = "6.0.1"
|
||||
rpc-toolkit = "0.2.0"
|
||||
rust-argon2 = "1.0.0"
|
||||
scopeguard = "1.1" # because avahi-sys fucks your shit up
|
||||
serde = { version = "1.0.130", features = ["derive", "rc"] }
|
||||
serde = { version = "1.0.139", features = ["derive", "rc"] }
|
||||
serde_cbor = { package = "ciborium", version = "0.2.0" }
|
||||
serde_json = "1.0.68"
|
||||
serde_toml = { package = "toml", version = "0.5.8" }
|
||||
serde_yaml = "0.8.21"
|
||||
serde_json = "1.0.82"
|
||||
serde_toml = { package = "toml", version = "0.5.9" }
|
||||
serde_with = { version = "1.14.0", features = ["macros", "json"] }
|
||||
serde_yaml = "0.8.25"
|
||||
sha2 = "0.10.2"
|
||||
sha2-old = { package = "sha2", version = "0.9.8" }
|
||||
simple-logging = "2.0"
|
||||
sqlx = { version = "0.5.11", features = [
|
||||
sha2-old = { package = "sha2", version = "0.9.9" }
|
||||
simple-logging = "2.0.2"
|
||||
sqlx = { version = "0.6.0", features = [
|
||||
"chrono",
|
||||
"offline",
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
] }
|
||||
stderrlog = "0.5.1"
|
||||
tar = "0.4.37"
|
||||
thiserror = "1.0.29"
|
||||
tokio = { version = "1.15.0", features = ["full"] }
|
||||
tokio-compat-02 = "0.2.0"
|
||||
tokio-stream = { version = "0.1.7", features = ["io-util", "sync"] }
|
||||
stderrlog = "0.5.3"
|
||||
tar = "0.4.38"
|
||||
thiserror = "1.0.31"
|
||||
tokio = { version = "1.19.2", features = ["full"] }
|
||||
tokio-stream = { version = "0.1.9", features = ["io-util", "sync"] }
|
||||
tokio-tar = { git = "https://github.com/dr-bonez/tokio-tar.git" }
|
||||
tokio-tungstenite = "0.14.0"
|
||||
tokio-util = { version = "0.6.8", features = ["io"] }
|
||||
torut = "0.2.0"
|
||||
tracing = "0.1"
|
||||
tracing-error = "0.1"
|
||||
tracing-futures = "0.2"
|
||||
tracing-subscriber = "0.2"
|
||||
tokio-tungstenite = "0.17.1"
|
||||
tokio-util = { version = "0.7.3", features = ["io"] }
|
||||
torut = "0.2.1"
|
||||
tracing = "0.1.35"
|
||||
tracing-error = "0.2.0"
|
||||
tracing-futures = "0.2.5"
|
||||
tracing-subscriber = { version = "0.3.14", features = ["env-filter"] }
|
||||
trust-dns-server = "0.21.2"
|
||||
typed-builder = "0.9.1"
|
||||
typed-builder = "0.10.0"
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
|
||||
[dependencies.serde_with]
|
||||
features = ["macros", "json"]
|
||||
version = "1.10.0"
|
||||
|
||||
[profile.dev.package.backtrace]
|
||||
opt-level = 3
|
||||
|
||||
@@ -18,4 +18,7 @@ alias 'rust-arm64-builder'='docker run $USE_TTY --rm -v "$HOME/.cargo/registry":
|
||||
cd ..
|
||||
rust-arm64-builder sh -c "(cd backend && cargo build)"
|
||||
cd backend
|
||||
|
||||
sudo chown -R $USER target
|
||||
sudo chown -R $USER ~/.cargo
|
||||
#rust-arm64-builder aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/embassyd
|
||||
|
||||
@@ -18,3 +18,6 @@ alias 'rust-musl-builder'='docker run $USE_TTY --rm -v "$HOME"/.cargo/registry:/
|
||||
cd ..
|
||||
rust-musl-builder sh -c "(cd backend && cargo +beta build --target=x86_64-unknown-linux-musl --no-default-features)"
|
||||
cd backend
|
||||
|
||||
sudo chown -R $USER target
|
||||
sudo chown -R $USER ~/.cargo
|
||||
@@ -18,3 +18,6 @@ alias 'rust-musl-builder'='docker run $USE_TTY --rm -v "$HOME"/.cargo/registry:/
|
||||
cd ..
|
||||
rust-musl-builder sh -c "(cd backend && cargo +beta build --release --target=x86_64-unknown-linux-musl --no-default-features)"
|
||||
cd backend
|
||||
|
||||
sudo chown -R $USER target
|
||||
sudo chown -R $USER ~/.cargo
|
||||
@@ -30,4 +30,8 @@ else
|
||||
rust-arm64-builder sh -c "(git config --global --add safe.directory '*'; cd backend && cargo build --release --features $FLAGS)"
|
||||
fi
|
||||
cd backend
|
||||
|
||||
sudo chown -R $USER target
|
||||
sudo chown -R $USER ~/.cargo
|
||||
|
||||
#rust-arm64-builder aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/embassyd
|
||||
|
||||
@@ -9,6 +9,8 @@ Type=oneshot
|
||||
Environment=RUST_LOG=embassy_init=debug,embassy=debug,js_engine=debug
|
||||
ExecStart=/usr/local/bin/embassy-init
|
||||
RemainAfterExit=true
|
||||
StandardOutput=file:/var/log/embassy-init.out.log
|
||||
StandardError=file:/var/log/embassy-init.error.log
|
||||
|
||||
[Install]
|
||||
WantedBy=embassyd.service
|
||||
|
||||
@@ -9,7 +9,7 @@ use tracing::instrument;
|
||||
|
||||
use crate::config::{Config, ConfigSpec};
|
||||
use crate::context::RpcContext;
|
||||
use crate::id::{ ImageId};
|
||||
use crate::id::ImageId;
|
||||
use crate::procedure::{PackageProcedure, ProcedureName};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat};
|
||||
@@ -57,9 +57,14 @@ pub struct Action {
|
||||
}
|
||||
impl Action {
|
||||
#[instrument]
|
||||
pub fn validate(&self, volumes: &Volumes, image_ids: &BTreeSet<ImageId>) -> Result<(), Error> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
) -> Result<(), Error> {
|
||||
self.implementation
|
||||
.validate(volumes, image_ids, true)
|
||||
.validate(eos_version, volumes, image_ids, true)
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
@@ -99,7 +104,7 @@ impl Action {
|
||||
}
|
||||
}
|
||||
|
||||
fn display_action_result(action_result: ActionResult, matches: &ArgMatches<'_>) {
|
||||
fn display_action_result(action_result: ActionResult, matches: &ArgMatches) {
|
||||
if matches.is_present("format") {
|
||||
return display_serializable(action_result, matches);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ pub fn auth() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parse_metadata(_: &str, _: &ArgMatches<'_>) -> Result<Value, Error> {
|
||||
pub fn parse_metadata(_: &str, _: &ArgMatches) -> Result<Value, Error> {
|
||||
Ok(serde_json::json!({
|
||||
"platforms": ["cli"],
|
||||
}))
|
||||
@@ -52,7 +52,7 @@ async fn cli_login(
|
||||
let password = if let Some(password) = password {
|
||||
password
|
||||
} else {
|
||||
rpassword::prompt_password_stdout("Password: ")?
|
||||
rpassword::prompt_password("Password: ")?
|
||||
};
|
||||
|
||||
rpc_toolkit::command_helpers::call_remote(
|
||||
@@ -169,7 +169,7 @@ pub async fn session() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_sessions(arg: SessionList, matches: &ArgMatches<'_>) {
|
||||
fn display_sessions(arg: SessionList, matches: &ArgMatches) {
|
||||
use prettytable::*;
|
||||
|
||||
if matches.is_present("format") {
|
||||
@@ -235,7 +235,7 @@ pub async fn list(
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches<'_>) -> Result<Vec<String>, RpcError> {
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result<Vec<String>, RpcError> {
|
||||
Ok(arg.split(",").map(|s| s.trim().to_owned()).collect())
|
||||
}
|
||||
|
||||
@@ -267,14 +267,14 @@ async fn cli_reset_password(
|
||||
let old_password = if let Some(old_password) = old_password {
|
||||
old_password
|
||||
} else {
|
||||
rpassword::prompt_password_stdout("Current Password: ")?
|
||||
rpassword::prompt_password("Current Password: ")?
|
||||
};
|
||||
|
||||
let new_password = if let Some(new_password) = new_password {
|
||||
new_password
|
||||
} else {
|
||||
let new_password = rpassword::prompt_password_stdout("New Password: ")?;
|
||||
if new_password != rpassword::prompt_password_stdout("Confirm: ")? {
|
||||
let new_password = rpassword::prompt_password("New Password: ")?;
|
||||
if new_password != rpassword::prompt_password("Confirm: ")? {
|
||||
return Err(Error::new(
|
||||
eyre!("Passwords do not match"),
|
||||
crate::ErrorKind::IncorrectPassword,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::AtomicFile;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::x509::X509;
|
||||
use patch_db::{DbHandle, LockType, PatchDbHandle, Revision};
|
||||
@@ -27,10 +29,10 @@ use crate::disk::mount::guard::TmpMountGuard;
|
||||
use crate::notifications::NotificationLevel;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::status::MainStatus;
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::util::{display_none, AtomicFile};
|
||||
use crate::version::VersionT;
|
||||
use crate::Error;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OsBackup {
|
||||
@@ -114,7 +116,7 @@ impl Serialize for OsBackup {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches<'_>) -> Result<BTreeSet<PackageId>, Error> {
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result<BTreeSet<PackageId>, Error> {
|
||||
arg.split(',')
|
||||
.map(|s| s.trim().parse().map_err(Error::from))
|
||||
.collect()
|
||||
@@ -364,6 +366,7 @@ async fn perform_backup<Db: DbHandle>(
|
||||
.backup
|
||||
.create(
|
||||
ctx,
|
||||
&mut tx,
|
||||
&package_id,
|
||||
&manifest.title,
|
||||
&manifest.version,
|
||||
@@ -424,7 +427,12 @@ async fn perform_backup<Db: DbHandle>(
|
||||
.await?;
|
||||
|
||||
let (root_ca_key, root_ca_cert) = ctx.net_controller.ssl.export_root_ca().await?;
|
||||
let mut os_backup_file = AtomicFile::new(backup_guard.as_ref().join("os-backup.cbor")).await?;
|
||||
let mut os_backup_file = AtomicFile::new(
|
||||
backup_guard.as_ref().join("os-backup.cbor"),
|
||||
None::<PathBuf>,
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
os_backup_file
|
||||
.write_all(
|
||||
&IoFormat::Cbor.to_vec(&OsBackup {
|
||||
@@ -439,7 +447,10 @@ async fn perform_backup<Db: DbHandle>(
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
os_backup_file.save().await?;
|
||||
os_backup_file
|
||||
.save()
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
|
||||
let timestamp = Some(Utc::now());
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::AtomicFile;
|
||||
use patch_db::{DbHandle, HasModel, LockType};
|
||||
use reqwest::Url;
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Executor, Sqlite};
|
||||
@@ -20,10 +22,10 @@ use crate::net::interface::{InterfaceId, Interfaces};
|
||||
use crate::procedure::{NoOutput, PackageProcedure, ProcedureName};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::util::{AtomicFile, Version};
|
||||
use crate::util::Version;
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::volume::{backup_dir, Volume, VolumeId, Volumes, BACKUP_DIR};
|
||||
use crate::{Error, ResultExt};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
pub mod backup_bulk;
|
||||
pub mod restore;
|
||||
@@ -60,6 +62,7 @@ pub fn package_backup() -> Result<(), Error> {
|
||||
struct BackupMetadata {
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub tor_keys: BTreeMap<InterfaceId, String>,
|
||||
pub marketplace_url: Option<Url>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
|
||||
@@ -68,20 +71,26 @@ pub struct BackupActions {
|
||||
pub restore: PackageProcedure,
|
||||
}
|
||||
impl BackupActions {
|
||||
pub fn validate(&self, volumes: &Volumes, image_ids: &BTreeSet<ImageId>) -> Result<(), Error> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
) -> Result<(), Error> {
|
||||
self.create
|
||||
.validate(volumes, image_ids, false)
|
||||
.validate(eos_version, volumes, image_ids, false)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Create"))?;
|
||||
self.restore
|
||||
.validate(volumes, image_ids, false)
|
||||
.validate(eos_version, volumes, image_ids, false)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Restore"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx))]
|
||||
pub async fn create(
|
||||
#[instrument(skip(ctx, db))]
|
||||
pub async fn create<Db: DbHandle>(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
pkg_id: &PackageId,
|
||||
pkg_title: &str,
|
||||
pkg_version: &Version,
|
||||
@@ -119,6 +128,18 @@ impl BackupActions {
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let marketplace_url = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(pkg_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?
|
||||
.marketplace_url()
|
||||
.get(db, true)
|
||||
.await?
|
||||
.into_owned();
|
||||
let tmp_path = Path::new(BACKUP_DIR)
|
||||
.join(pkg_id)
|
||||
.join(format!("{}.s9pk", pkg_id));
|
||||
@@ -129,7 +150,9 @@ impl BackupActions {
|
||||
.join(pkg_version.as_str())
|
||||
.join(format!("{}.s9pk", pkg_id));
|
||||
let mut infile = File::open(&s9pk_path).await?;
|
||||
let mut outfile = AtomicFile::new(&tmp_path).await?;
|
||||
let mut outfile = AtomicFile::new(&tmp_path, None::<PathBuf>)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
tokio::io::copy(&mut infile, &mut *outfile)
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
@@ -138,17 +161,20 @@ impl BackupActions {
|
||||
format!("cp {} -> {}", s9pk_path.display(), tmp_path.display()),
|
||||
)
|
||||
})?;
|
||||
outfile.save().await?;
|
||||
outfile.save().await.with_kind(ErrorKind::Filesystem)?;
|
||||
let timestamp = Utc::now();
|
||||
let metadata_path = Path::new(BACKUP_DIR).join(pkg_id).join("metadata.cbor");
|
||||
let mut outfile = AtomicFile::new(&metadata_path).await?;
|
||||
let mut outfile = AtomicFile::new(&metadata_path, None::<PathBuf>)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
outfile
|
||||
.write_all(&IoFormat::Cbor.to_vec(&BackupMetadata {
|
||||
timestamp,
|
||||
tor_keys,
|
||||
marketplace_url,
|
||||
})?)
|
||||
.await?;
|
||||
outfile.save().await?;
|
||||
outfile.save().await.with_kind(ErrorKind::Filesystem)?;
|
||||
Ok(PackageBackupInfo {
|
||||
os_version: Current::new().semver().into(),
|
||||
title: pkg_title.to_owned(),
|
||||
@@ -217,17 +243,21 @@ impl BackupActions {
|
||||
.package_data()
|
||||
.lock(db, LockType::Write)
|
||||
.await?;
|
||||
crate::db::DatabaseModel::new()
|
||||
let pde = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(pkg_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?
|
||||
.await?;
|
||||
pde.clone()
|
||||
.interface_addresses()
|
||||
.put(db, &interfaces.install(&mut *secrets, pkg_id).await?)
|
||||
.await?;
|
||||
pde.marketplace_url()
|
||||
.put(db, &metadata.marketplace_url)
|
||||
.await?;
|
||||
|
||||
let entry = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
|
||||
@@ -37,7 +37,7 @@ use crate::util::serde::IoFormat;
|
||||
use crate::volume::{backup_dir, BACKUP_DIR, PKG_VOLUME_DIR};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches<'_>) -> Result<Vec<PackageId>, Error> {
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result<Vec<PackageId>, Error> {
|
||||
arg.split(',')
|
||||
.map(|s| s.trim().parse().map_err(Error::from))
|
||||
.collect()
|
||||
@@ -64,8 +64,39 @@ pub async fn restore_packages_rpc(
|
||||
let (revision, backup_guard, tasks, _) =
|
||||
restore_packages(&ctx, &mut db, backup_guard, ids).await?;
|
||||
|
||||
tokio::spawn(async {
|
||||
futures::future::join_all(tasks).await;
|
||||
tokio::spawn(async move {
|
||||
let res = futures::future::join_all(tasks).await;
|
||||
for res in res {
|
||||
match res.with_kind(crate::ErrorKind::Unknown) {
|
||||
Ok((Ok(_), _)) => (),
|
||||
Ok((Err(err), package_id)) => {
|
||||
if let Err(err) = ctx.notification_manager.notify(
|
||||
&mut db,
|
||||
Some(package_id.clone()),
|
||||
NotificationLevel::Error,
|
||||
"Restoration Failure".to_string(), format!("Error restoring package {}: {}", package_id,err), (), None).await{
|
||||
tracing::error!("Failed to notify: {}", err);
|
||||
tracing::debug!("{:?}", err);
|
||||
};
|
||||
tracing::error!("Error restoring package {}: {}", package_id, err);
|
||||
tracing::debug!("{:?}", err);
|
||||
},
|
||||
Err(e) => {
|
||||
if let Err(err) = ctx.notification_manager.notify(
|
||||
&mut db,
|
||||
None,
|
||||
NotificationLevel::Error,
|
||||
"Restoration Failure".to_string(), format!("Error during restoration: {}", e), (), None).await {
|
||||
|
||||
tracing::error!("Failed to notify: {}", err);
|
||||
tracing::debug!("{:?}", err);
|
||||
}
|
||||
tracing::error!("Error restoring packages: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
if let Err(e) = backup_guard.unmount().await {
|
||||
tracing::error!("Error unmounting backup drive: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
@@ -238,7 +269,7 @@ pub async fn recover_full_embassy(
|
||||
&mut db,
|
||||
None,
|
||||
NotificationLevel::Error,
|
||||
"Restoration Failure".to_string(), format!("Error restoring ?: {}", e), (), None).await {
|
||||
"Restoration Failure".to_string(), format!("Error during restoration: {}", e), (), None).await {
|
||||
|
||||
tracing::error!("Failed to notify: {}", err);
|
||||
tracing::debug!("{:?}", err);
|
||||
|
||||
@@ -6,7 +6,7 @@ use chrono::{DateTime, Utc};
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::{Digest, OutputSizeUser};
|
||||
use digest::OutputSizeUser;
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
@@ -186,7 +186,7 @@ pub struct PackageBackupInfo {
|
||||
pub timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
fn display_backup_info(info: BackupInfo, matches: &ArgMatches<'_>) {
|
||||
fn display_backup_info(info: BackupInfo, matches: &ArgMatches) {
|
||||
use prettytable::*;
|
||||
|
||||
if matches.is_present("format") {
|
||||
|
||||
@@ -7,20 +7,24 @@ use rpc_toolkit::run_cli;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use serde_json::Value;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref VERSION_STRING: String = Current::new().semver().to_string();
|
||||
}
|
||||
|
||||
fn inner_main() -> Result<(), Error> {
|
||||
run_cli!({
|
||||
command: embassy::main_api,
|
||||
app: app => app
|
||||
.name("Embassy CLI")
|
||||
.version(Current::new().semver().to_string().as_str())
|
||||
.version(&**VERSION_STRING)
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
.short("c")
|
||||
.short('c')
|
||||
.long("config")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(Arg::with_name("host").long("host").short("h").takes_value(true))
|
||||
.arg(Arg::with_name("proxy").long("proxy").short("p").takes_value(true)),
|
||||
.arg(Arg::with_name("host").long("host").short('h').takes_value(true))
|
||||
.arg(Arg::with_name("proxy").long("proxy").short('p').takes_value(true)),
|
||||
context: matches => {
|
||||
EmbassyLogger::init();
|
||||
CliContext::init(matches)?
|
||||
|
||||
@@ -8,6 +8,7 @@ use embassy::disk::fsck::RepairStrategy;
|
||||
use embassy::disk::main::DEFAULT_PASSWORD;
|
||||
use embassy::disk::REPAIR_DISK_PATH;
|
||||
use embassy::hostname::get_product_key;
|
||||
use embassy::init::STANDBY_MODE_PATH;
|
||||
use embassy::middleware::cors::cors;
|
||||
use embassy::middleware::diagnostic::diagnostic;
|
||||
use embassy::middleware::encrypt::encrypt;
|
||||
@@ -17,7 +18,7 @@ use embassy::shutdown::Shutdown;
|
||||
use embassy::sound::CHIME;
|
||||
use embassy::util::logger::EmbassyLogger;
|
||||
use embassy::util::Invoke;
|
||||
use embassy::{Error, ResultExt};
|
||||
use embassy::{Error, ErrorKind, ResultExt};
|
||||
use http::StatusCode;
|
||||
use rpc_toolkit::rpc_server;
|
||||
use tokio::process::Command;
|
||||
@@ -128,6 +129,13 @@ async fn run_script_if_exists<P: AsRef<Path>>(path: P) {
|
||||
|
||||
#[instrument]
|
||||
async fn inner_main(cfg_path: Option<&str>) -> Result<Option<Shutdown>, Error> {
|
||||
if tokio::fs::metadata(STANDBY_MODE_PATH).await.is_ok() {
|
||||
tokio::fs::remove_file(STANDBY_MODE_PATH).await?;
|
||||
Command::new("sync").invoke(ErrorKind::Filesystem).await?;
|
||||
embassy::sound::SHUTDOWN.play().await?;
|
||||
futures::future::pending::<()>().await;
|
||||
}
|
||||
|
||||
embassy::sound::BEP.play().await?;
|
||||
|
||||
run_script_if_exists("/embassy-os/preinit.sh").await;
|
||||
@@ -210,7 +218,7 @@ fn main() {
|
||||
let matches = clap::App::new("embassyd")
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
.short("c")
|
||||
.short('c')
|
||||
.long("config")
|
||||
.takes_value(true),
|
||||
)
|
||||
|
||||
@@ -6,15 +6,19 @@ use rpc_toolkit::run_cli;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use serde_json::Value;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref VERSION_STRING: String = Current::new().semver().to_string();
|
||||
}
|
||||
|
||||
fn inner_main() -> Result<(), Error> {
|
||||
run_cli!({
|
||||
command: embassy::portable_api,
|
||||
app: app => app
|
||||
.name("Embassy SDK")
|
||||
.version(Current::new().semver().to_string().as_str())
|
||||
.version(&**VERSION_STRING)
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
.short("c")
|
||||
.short('c')
|
||||
.long("config")
|
||||
.takes_value(true),
|
||||
),
|
||||
|
||||
@@ -287,7 +287,7 @@ fn main() {
|
||||
let matches = clap::App::new("embassyd")
|
||||
.arg(
|
||||
clap::Arg::with_name("config")
|
||||
.short("c")
|
||||
.short('c')
|
||||
.long("config")
|
||||
.takes_value(true),
|
||||
)
|
||||
@@ -343,6 +343,7 @@ fn main() {
|
||||
e,
|
||||
)
|
||||
.await?;
|
||||
let mut shutdown = ctx.shutdown.subscribe();
|
||||
rpc_server!({
|
||||
command: embassy::diagnostic_api,
|
||||
context: ctx.clone(),
|
||||
@@ -360,7 +361,7 @@ fn main() {
|
||||
})
|
||||
.await
|
||||
.with_kind(embassy::ErrorKind::Network)?;
|
||||
Ok::<_, Error>(None)
|
||||
Ok::<_, Error>(shutdown.recv().await.with_kind(crate::ErrorKind::Unknown)?)
|
||||
})()
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -31,12 +31,17 @@ pub struct ConfigActions {
|
||||
}
|
||||
impl ConfigActions {
|
||||
#[instrument]
|
||||
pub fn validate(&self, volumes: &Volumes, image_ids: &BTreeSet<ImageId>) -> Result<(), Error> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
) -> Result<(), Error> {
|
||||
self.get
|
||||
.validate(volumes, image_ids, true)
|
||||
.validate(eos_version, volumes, image_ids, true)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Config Get"))?;
|
||||
self.set
|
||||
.validate(volumes, image_ids, true)
|
||||
.validate(eos_version, volumes, image_ids, true)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Config Set"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ impl CharSet {
|
||||
self.0.iter().any(|r| r.0.contains(c))
|
||||
}
|
||||
pub fn gen<R: Rng>(&self, rng: &mut R) -> char {
|
||||
let mut idx = rng.gen_range(0, self.1);
|
||||
let mut idx = rng.gen_range(0..self.1);
|
||||
for r in &self.0 {
|
||||
if idx < r.1 {
|
||||
return std::convert::TryFrom::try_from(
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use bollard::Docker;
|
||||
use helpers::to_tmp_path;
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::{DbHandle, LockReceipt, LockType, PatchDb, Revision};
|
||||
use reqwest::Url;
|
||||
@@ -35,7 +36,7 @@ use crate::shutdown::Shutdown;
|
||||
use crate::status::{MainStatus, Status};
|
||||
use crate::util::io::from_yaml_async_reader;
|
||||
use crate::util::{AsyncFileExt, Invoke};
|
||||
use crate::{Error, ResultExt};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
@@ -336,6 +337,18 @@ impl RpcContext {
|
||||
static_files,
|
||||
manifest,
|
||||
} => {
|
||||
for (volume_id, volume_info) in &*manifest.volumes {
|
||||
let tmp_path = to_tmp_path(volume_info.path_for(
|
||||
&self.datadir,
|
||||
&package_id,
|
||||
&manifest.version,
|
||||
&volume_id,
|
||||
))
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
if tokio::fs::metadata(&tmp_path).await.is_ok() {
|
||||
tokio::fs::remove_dir_all(&tmp_path).await?;
|
||||
}
|
||||
}
|
||||
let status = installed.status;
|
||||
let main = match status.main {
|
||||
MainStatus::BackingUp { started, .. } => {
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn init(#[context] ctx: SdkContext) -> Result<(), Error> {
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, parent.display().to_string()))?;
|
||||
}
|
||||
tracing::info!("Generating new developer key...");
|
||||
let keypair = Keypair::generate(&mut rand::thread_rng());
|
||||
let keypair = Keypair::generate(&mut rand_old::thread_rng());
|
||||
tracing::info!("Writing key to {}", ctx.developer_key_path.display());
|
||||
let keypair_bytes = ed25519::KeypairBytes {
|
||||
secret_key: keypair.secret.to_bytes(),
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::disk::mount::filesystem::block_dev::mount;
|
||||
use crate::disk::mount::filesystem::ReadWrite;
|
||||
use crate::disk::mount::util::unmount;
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ResultExt};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
pub const PASSWORD_PATH: &'static str = "/etc/embassy/password";
|
||||
pub const DEFAULT_PASSWORD: &'static str = "password";
|
||||
@@ -183,6 +183,7 @@ pub async fn unmount_all_fs<P: AsRef<Path>>(guid: &str, datadir: P) -> Result<()
|
||||
|
||||
#[instrument(skip(datadir))]
|
||||
pub async fn export<P: AsRef<Path>>(guid: &str, datadir: P) -> Result<(), Error> {
|
||||
Command::new("sync").invoke(ErrorKind::Filesystem).await?;
|
||||
unmount_all_fs(guid, datadir).await?;
|
||||
Command::new("vgchange")
|
||||
.arg("-an")
|
||||
|
||||
@@ -20,7 +20,7 @@ pub fn disk() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_disk_info(info: DiskListResponse, matches: &ArgMatches<'_>) {
|
||||
fn display_disk_info(info: DiskListResponse, matches: &ArgMatches) {
|
||||
use prettytable::*;
|
||||
|
||||
if matches.is_present("format") {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::AtomicFile;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -14,9 +15,9 @@ use crate::disk::util::EmbassyOsRecoveryInfo;
|
||||
use crate::middleware::encrypt::{decrypt_slice, encrypt_slice};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::util::{AtomicFile, FileLock};
|
||||
use crate::util::FileLock;
|
||||
use crate::volume::BACKUP_DIR;
|
||||
use crate::{Error, ResultExt};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
pub struct BackupMountGuard<G: GenericMountGuard> {
|
||||
backup_disk_mount_guard: Option<G>,
|
||||
@@ -162,16 +163,20 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
|
||||
pub async fn save(&self) -> Result<(), Error> {
|
||||
let metadata_path = self.as_ref().join("metadata.cbor");
|
||||
let backup_disk_path = self.backup_disk_path();
|
||||
let mut file = AtomicFile::new(&metadata_path).await?;
|
||||
let mut file = AtomicFile::new(&metadata_path, None::<PathBuf>)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
file.write_all(&IoFormat::Cbor.to_vec(&self.metadata)?)
|
||||
.await?;
|
||||
file.save().await?;
|
||||
file.save().await.with_kind(ErrorKind::Filesystem)?;
|
||||
let unencrypted_metadata_path =
|
||||
backup_disk_path.join("EmbassyBackups/unencrypted-metadata.cbor");
|
||||
let mut file = AtomicFile::new(&unencrypted_metadata_path).await?;
|
||||
let mut file = AtomicFile::new(&unencrypted_metadata_path, None::<PathBuf>)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
file.write_all(&IoFormat::Cbor.to_vec(&self.unencrypted_metadata)?)
|
||||
.await?;
|
||||
file.save().await?;
|
||||
file.save().await.with_kind(ErrorKind::Filesystem)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::num::ParseIntError;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::AtomicFile;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::BOOT_RW_PATH;
|
||||
use crate::util::AtomicFile;
|
||||
use crate::Error;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
pub const QUIRK_PATH: &'static str = "/sys/module/usb_storage/parameters/quirks";
|
||||
|
||||
@@ -160,11 +160,13 @@ pub async fn save_quirks(quirks: &Quirks) -> Result<(), Error> {
|
||||
tokio::fs::copy(&target_path, &orig_path).await?;
|
||||
}
|
||||
let cmdline = tokio::fs::read_to_string(&orig_path).await?;
|
||||
let mut target = AtomicFile::new(&target_path).await?;
|
||||
let mut target = AtomicFile::new(&target_path, None::<PathBuf>)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
target
|
||||
.write_all(format!("usb-storage.quirks={} {}", quirks, cmdline).as_bytes())
|
||||
.await?;
|
||||
target.save().await?;
|
||||
target.save().await.with_kind(ErrorKind::Filesystem)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::util::Invoke;
|
||||
use crate::Error;
|
||||
|
||||
pub const SYSTEM_REBUILD_PATH: &str = "/embassy-os/system-rebuild";
|
||||
pub const STANDBY_MODE_PATH: &str = "/embassy-os/standby";
|
||||
|
||||
pub async fn check_time_is_synchronized() -> Result<bool, Error> {
|
||||
Ok(String::from_utf8(
|
||||
@@ -160,6 +161,8 @@ pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error
|
||||
}
|
||||
if warn_time_not_synced {
|
||||
tracing::warn!("Timed out waiting for system time to synchronize");
|
||||
} else {
|
||||
tracing::info!("Syncronized system clock");
|
||||
}
|
||||
|
||||
crate::version::init(&mut handle, &receipts).await?;
|
||||
@@ -168,5 +171,7 @@ pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error
|
||||
tokio::fs::remove_file(SYSTEM_REBUILD_PATH).await?;
|
||||
}
|
||||
|
||||
tracing::info!("System initialized.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::s9pk::reader::S9pkReader;
|
||||
use crate::status::{MainStatus, Status};
|
||||
use crate::util::io::{copy_and_shutdown, response_to_reader};
|
||||
use crate::util::serde::{display_serializable, IoFormat, Port};
|
||||
use crate::util::serde::{display_serializable, Port};
|
||||
use crate::util::{display_none, AsyncFileExt, Version};
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::volume::{asset_dir, script_dir};
|
||||
@@ -121,9 +121,9 @@ impl std::fmt::Display for MinMax {
|
||||
pub async fn install(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg] id: String,
|
||||
#[arg(short = "m", long = "marketplace-url", rename = "marketplace-url")]
|
||||
#[arg(short = 'm', long = "marketplace-url", rename = "marketplace-url")]
|
||||
marketplace_url: Option<Url>,
|
||||
#[arg(short = "v", long = "version-spec", rename = "version-spec")] version_spec: Option<
|
||||
#[arg(short = 'v', long = "version-spec", rename = "version-spec")] version_spec: Option<
|
||||
String,
|
||||
>,
|
||||
#[arg(long = "version-priority", rename = "version-priority")] version_priority: Option<MinMax>,
|
||||
@@ -143,7 +143,7 @@ pub async fn install(
|
||||
version,
|
||||
version_priority,
|
||||
Current::new().compat(),
|
||||
platforms::TARGET_ARCH,
|
||||
&*crate::ARCH,
|
||||
))
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Registry)?
|
||||
@@ -159,7 +159,7 @@ pub async fn install(
|
||||
man.version,
|
||||
version_priority,
|
||||
Current::new().compat(),
|
||||
platforms::TARGET_ARCH,
|
||||
&*crate::ARCH,
|
||||
))
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Registry)?
|
||||
@@ -191,7 +191,7 @@ pub async fn install(
|
||||
id,
|
||||
man.version,
|
||||
Current::new().compat(),
|
||||
platforms::TARGET_ARCH,
|
||||
&*crate::ARCH,
|
||||
))
|
||||
.await?
|
||||
.error_for_status()?,
|
||||
@@ -210,7 +210,7 @@ pub async fn install(
|
||||
id,
|
||||
man.version,
|
||||
Current::new().compat(),
|
||||
platforms::TARGET_ARCH,
|
||||
&*crate::ARCH,
|
||||
))
|
||||
.await?
|
||||
.error_for_status()?,
|
||||
@@ -229,7 +229,7 @@ pub async fn install(
|
||||
id,
|
||||
man.version,
|
||||
Current::new().compat(),
|
||||
platforms::TARGET_ARCH,
|
||||
&*crate::ARCH,
|
||||
))
|
||||
.await?
|
||||
.error_for_status()?,
|
||||
@@ -959,7 +959,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
dep,
|
||||
info.version,
|
||||
Current::new().compat(),
|
||||
platforms::TARGET_ARCH,
|
||||
&*crate::ARCH,
|
||||
))
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Registry)?
|
||||
@@ -994,7 +994,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
dep,
|
||||
info.version,
|
||||
Current::new().compat(),
|
||||
platforms::TARGET_ARCH,
|
||||
&*crate::ARCH,
|
||||
))
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Registry)?;
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
pub const DEFAULT_MARKETPLACE: &str = "https://marketplace.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;
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref ARCH: &'static str = {
|
||||
let (arch, _) = TARGET.split_once("-").unwrap();
|
||||
arch
|
||||
};
|
||||
}
|
||||
|
||||
pub mod action;
|
||||
pub mod auth;
|
||||
|
||||
@@ -111,7 +111,7 @@ pub enum LogSource {
|
||||
Container(PackageId),
|
||||
}
|
||||
|
||||
pub fn display_logs(all: LogResponse, _: &ArgMatches<'_>) {
|
||||
pub fn display_logs(all: LogResponse, _: &ArgMatches) {
|
||||
for entry in all.entries.iter() {
|
||||
println!("{}", entry);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,95 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use patch_db::{DbHandle, LockType};
|
||||
use patch_db::{DbHandle, LockReceipt, LockType};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::CurrentDependents;
|
||||
use crate::dependencies::{break_transitive, heal_transitive, DependencyError};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::status::health_check::{HealthCheckId, HealthCheckResult};
|
||||
use crate::status::MainStatus;
|
||||
use crate::Error;
|
||||
|
||||
struct HealthCheckPreInformationReceipt {
|
||||
status_model: LockReceipt<MainStatus, ()>,
|
||||
manifest: LockReceipt<Manifest, ()>,
|
||||
}
|
||||
impl HealthCheckPreInformationReceipt {
|
||||
pub async fn new(db: &'_ mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks, id);
|
||||
setup(&db.lock_all(locks).await?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
id: &PackageId,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let status_model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.status().main())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
let manifest = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.manifest())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
status_model: status_model.verify(skeleton_key)?,
|
||||
manifest: manifest.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HealthCheckStatusReceipt {
|
||||
status: LockReceipt<MainStatus, ()>,
|
||||
current_dependents: LockReceipt<CurrentDependents, ()>,
|
||||
}
|
||||
impl HealthCheckStatusReceipt {
|
||||
pub async fn new(db: &'_ mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks, id);
|
||||
setup(&db.lock_all(locks).await?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
id: &PackageId,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let status = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.status().main())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let current_dependents = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.current_dependents())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
status: status.verify(skeleton_key)?,
|
||||
current_dependents: current_dependents.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, db))]
|
||||
pub async fn check<Db: DbHandle>(
|
||||
ctx: &RpcContext,
|
||||
@@ -19,35 +98,17 @@ pub async fn check<Db: DbHandle>(
|
||||
should_commit: &AtomicBool,
|
||||
) -> Result<(), Error> {
|
||||
let mut tx = db.begin().await?;
|
||||
let (manifest, started) = {
|
||||
let mut checkpoint = tx.begin().await?;
|
||||
let receipts = HealthCheckPreInformationReceipt::new(&mut checkpoint, id).await?;
|
||||
|
||||
let mut checkpoint = tx.begin().await?;
|
||||
let manifest = receipts.manifest.get(&mut checkpoint).await?;
|
||||
|
||||
let installed_model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.expect(&mut checkpoint)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(&mut checkpoint)
|
||||
.await?;
|
||||
let started = receipts.status_model.get(&mut checkpoint).await?.started();
|
||||
|
||||
let manifest = installed_model
|
||||
.clone()
|
||||
.manifest()
|
||||
.get(&mut checkpoint, true)
|
||||
.await?
|
||||
.into_owned();
|
||||
|
||||
let started = installed_model
|
||||
.clone()
|
||||
.status()
|
||||
.main()
|
||||
.started()
|
||||
.get(&mut checkpoint, true)
|
||||
.await?
|
||||
.into_owned();
|
||||
|
||||
checkpoint.save().await?;
|
||||
checkpoint.save().await?;
|
||||
(manifest, started)
|
||||
};
|
||||
|
||||
let health_results = if let Some(started) = started {
|
||||
manifest
|
||||
@@ -61,48 +122,38 @@ pub async fn check<Db: DbHandle>(
|
||||
if !should_commit.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
let current_dependents = {
|
||||
let mut checkpoint = tx.begin().await?;
|
||||
let receipts = HealthCheckStatusReceipt::new(&mut checkpoint, id).await?;
|
||||
|
||||
let mut checkpoint = tx.begin().await?;
|
||||
let status = receipts.status.get(&mut checkpoint).await?;
|
||||
|
||||
crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.lock(&mut checkpoint, LockType::Write)
|
||||
.await?;
|
||||
|
||||
let mut status = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.expect(&mut checkpoint)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(&mut checkpoint)
|
||||
.await?
|
||||
.status()
|
||||
.main()
|
||||
.get_mut(&mut checkpoint)
|
||||
.await?;
|
||||
|
||||
match &mut *status {
|
||||
MainStatus::Running { health, .. } => {
|
||||
*health = health_results.clone();
|
||||
match status {
|
||||
MainStatus::Running { health, started } => {
|
||||
receipts
|
||||
.status
|
||||
.set(
|
||||
&mut checkpoint,
|
||||
MainStatus::Running {
|
||||
health: health_results.clone(),
|
||||
started,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
let current_dependents = receipts.current_dependents.get(&mut checkpoint).await?;
|
||||
|
||||
status.save(&mut checkpoint).await?;
|
||||
|
||||
let current_dependents = installed_model
|
||||
.current_dependents()
|
||||
.get(&mut checkpoint, true)
|
||||
.await?;
|
||||
|
||||
checkpoint.save().await?;
|
||||
checkpoint.save().await?;
|
||||
current_dependents
|
||||
};
|
||||
|
||||
tracing::debug!("Checking health of {}", id);
|
||||
let receipts = crate::dependencies::BreakTransitiveReceipts::new(&mut tx).await?;
|
||||
tracing::debug!("Got receipts {}", id);
|
||||
|
||||
for (dependent, info) in (*current_dependents).0.iter() {
|
||||
for (dependent, info) in (current_dependents).0.iter() {
|
||||
let failures: BTreeMap<HealthCheckId, HealthCheckResult> = health_results
|
||||
.iter()
|
||||
.filter(|(_, hc_res)| !matches!(hc_res, HealthCheckResult::Success { .. }))
|
||||
|
||||
@@ -229,7 +229,10 @@ async fn run_main(
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(bollard::errors::Error::DockerResponseNotFoundError { .. }) => (),
|
||||
Err(bollard::errors::Error::DockerResponseServerError {
|
||||
status_code: 404, // NOT FOUND
|
||||
..
|
||||
}) => (),
|
||||
Err(e) => Err(e)?,
|
||||
}
|
||||
match futures::poll!(&mut runtime) {
|
||||
@@ -375,8 +378,13 @@ impl Manager {
|
||||
.or_else(|e| {
|
||||
if matches!(
|
||||
e,
|
||||
bollard::errors::Error::DockerResponseConflictError { .. }
|
||||
| bollard::errors::Error::DockerResponseNotFoundError { .. }
|
||||
bollard::errors::Error::DockerResponseServerError {
|
||||
status_code: 409, // CONFLICT
|
||||
..
|
||||
} | bollard::errors::Error::DockerResponseServerError {
|
||||
status_code: 404, // NOT FOUND
|
||||
..
|
||||
}
|
||||
) {
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -413,9 +421,18 @@ impl Manager {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(bollard::errors::Error::DockerResponseNotFoundError { .. })
|
||||
| Err(bollard::errors::Error::DockerResponseConflictError { .. })
|
||||
| Err(bollard::errors::Error::DockerResponseNotModifiedError { .. }) => (), // Already stopped
|
||||
Err(bollard::errors::Error::DockerResponseServerError {
|
||||
status_code: 404, // NOT FOUND
|
||||
..
|
||||
})
|
||||
| Err(bollard::errors::Error::DockerResponseServerError {
|
||||
status_code: 409, // CONFLICT
|
||||
..
|
||||
})
|
||||
| Err(bollard::errors::Error::DockerResponseServerError {
|
||||
status_code: 304, // NOT MODIFIED
|
||||
..
|
||||
}) => (), // Already stopped
|
||||
a => a?,
|
||||
};
|
||||
self.shared.status.store(
|
||||
@@ -566,9 +583,18 @@ async fn stop(shared: &ManagerSharedState) -> Result<(), Error> {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(bollard::errors::Error::DockerResponseNotFoundError { .. })
|
||||
| Err(bollard::errors::Error::DockerResponseConflictError { .. })
|
||||
| Err(bollard::errors::Error::DockerResponseNotModifiedError { .. }) => (), // Already stopped
|
||||
Err(bollard::errors::Error::DockerResponseServerError {
|
||||
status_code: 404, // NOT FOUND
|
||||
..
|
||||
})
|
||||
| Err(bollard::errors::Error::DockerResponseServerError {
|
||||
status_code: 409, // CONFLICT
|
||||
..
|
||||
})
|
||||
| Err(bollard::errors::Error::DockerResponseServerError {
|
||||
status_code: 304, // NOT MODIFIED
|
||||
..
|
||||
}) => (), // Already stopped
|
||||
a => a?,
|
||||
};
|
||||
shared.status.store(
|
||||
|
||||
@@ -25,22 +25,31 @@ pub struct Migrations {
|
||||
}
|
||||
impl Migrations {
|
||||
#[instrument]
|
||||
pub fn validate(&self, volumes: &Volumes, image_ids: &BTreeSet<ImageId>) -> Result<(), Error> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
) -> Result<(), Error> {
|
||||
for (version, migration) in &self.from {
|
||||
migration.validate(volumes, image_ids, true).with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
format!("Migration from {}", version),
|
||||
)
|
||||
})?;
|
||||
migration
|
||||
.validate(eos_version, volumes, image_ids, true)
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
format!("Migration from {}", version),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
for (version, migration) in &self.to {
|
||||
migration.validate(volumes, image_ids, true).with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
format!("Migration to {}", version),
|
||||
)
|
||||
})?;
|
||||
migration
|
||||
.validate(eos_version, volumes, image_ids, true)
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
format!("Migration to {}", version),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@ use openssl::pkey::{PKey, Private};
|
||||
use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509};
|
||||
use openssl::*;
|
||||
use sqlx::SqlitePool;
|
||||
use tokio::process::Command;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
static CERTIFICATE_VERSION: i32 = 2; // X509 version 3 is actually encoded as '2' in the cert because fuck you.
|
||||
@@ -180,6 +182,17 @@ impl SslManager {
|
||||
)
|
||||
.await?;
|
||||
tokio::fs::write(ROOT_CA_STATIC_PATH, root_cert.to_pem()?).await?;
|
||||
|
||||
// write to ca cert store
|
||||
tokio::fs::write(
|
||||
"/usr/local/share/ca-certificates/embassy-root-ca.crt",
|
||||
root_cert.to_pem()?,
|
||||
)
|
||||
.await?;
|
||||
Command::new("update-ca-certificates")
|
||||
.invoke(crate::ErrorKind::OpenSsl)
|
||||
.await?;
|
||||
|
||||
let (int_key, int_cert) = match store.load_intermediate_certificate().await? {
|
||||
None => {
|
||||
let int_key = generate_key()?;
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn tor() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_services(services: Vec<OnionAddressV3>, matches: &ArgMatches<'_>) {
|
||||
fn display_services(services: Vec<OnionAddressV3>, matches: &ArgMatches) {
|
||||
use prettytable::*;
|
||||
|
||||
if matches.is_present("format") {
|
||||
|
||||
@@ -188,7 +188,7 @@ pub struct WifiListOut {
|
||||
security: Vec<String>,
|
||||
}
|
||||
pub type WifiList = HashMap<Ssid, WifiListInfo>;
|
||||
fn display_wifi_info(info: WiFiInfo, matches: &ArgMatches<'_>) {
|
||||
fn display_wifi_info(info: WiFiInfo, matches: &ArgMatches) {
|
||||
use prettytable::*;
|
||||
|
||||
if matches.is_present("format") {
|
||||
@@ -252,7 +252,7 @@ fn display_wifi_info(info: WiFiInfo, matches: &ArgMatches<'_>) {
|
||||
table_global.print_tty(false);
|
||||
}
|
||||
|
||||
fn display_wifi_list(info: Vec<WifiListOut>, matches: &ArgMatches<'_>) {
|
||||
fn display_wifi_list(info: Vec<WifiListOut>, matches: &ArgMatches) {
|
||||
use prettytable::*;
|
||||
|
||||
if matches.is_present("format") {
|
||||
@@ -764,7 +764,7 @@ pub async fn interface_connected(interface: &str) -> Result<bool, Error> {
|
||||
Ok(v.is_some())
|
||||
}
|
||||
|
||||
pub fn country_code_parse(code: &str, _matches: &ArgMatches<'_>) -> Result<CountryCode, Error> {
|
||||
pub fn country_code_parse(code: &str, _matches: &ArgMatches) -> Result<CountryCode, Error> {
|
||||
CountryCode::for_alpha2(code).map_err(|_| {
|
||||
Error::new(
|
||||
color_eyre::eyre::eyre!("Invalid Country Code: {}", code),
|
||||
|
||||
@@ -64,6 +64,7 @@ pub struct DockerProcedure {
|
||||
impl DockerProcedure {
|
||||
pub fn validate(
|
||||
&self,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
expected_io: bool,
|
||||
@@ -85,6 +86,12 @@ impl DockerProcedure {
|
||||
if expected_io && self.io_format.is_none() {
|
||||
color_eyre::eyre::bail!("expected io-format");
|
||||
}
|
||||
if &**eos_version >= &emver::Version::new(0, 3, 1, 1)
|
||||
&& self.inject
|
||||
&& !self.mounts.is_empty()
|
||||
{
|
||||
color_eyre::eyre::bail!("mounts not allowed in inject actions");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -127,7 +134,11 @@ impl DockerProcedure {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) | Err(bollard::errors::Error::DockerResponseNotFoundError { .. }) => Ok(()),
|
||||
Ok(())
|
||||
| Err(bollard::errors::Error::DockerResponseServerError {
|
||||
status_code: 404, // NOT FOUND
|
||||
..
|
||||
}) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}?;
|
||||
}
|
||||
@@ -193,7 +204,7 @@ impl DockerProcedure {
|
||||
match format.from_slice(buffer.as_bytes()) {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
tracing::trace!(
|
||||
"Failed to deserialize stdout from {}: {}, falling back to UTF-8 string.",
|
||||
format,
|
||||
e
|
||||
@@ -334,7 +345,7 @@ impl DockerProcedure {
|
||||
match format.from_slice(buffer.as_bytes()) {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
tracing::trace!(
|
||||
"Failed to deserialize stdout from {}: {}, falling back to UTF-8 string.",
|
||||
format,
|
||||
e
|
||||
|
||||
@@ -45,7 +45,10 @@ impl PathForVolumeId for Volumes {
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct JsProcedure {}
|
||||
pub struct JsProcedure {
|
||||
#[serde(default)]
|
||||
args: Vec<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl JsProcedure {
|
||||
pub fn validate(&self, _volumes: &Volumes) -> Result<(), color_eyre::eyre::Report> {
|
||||
@@ -71,7 +74,7 @@ impl JsProcedure {
|
||||
Box::new(volumes.clone()),
|
||||
)
|
||||
.await?
|
||||
.run_action(name, input);
|
||||
.run_action(name, input, self.args.clone());
|
||||
let output: ErrorValue = match timeout {
|
||||
Some(timeout_duration) => tokio::time::timeout(timeout_duration, running_action)
|
||||
.await
|
||||
@@ -105,7 +108,7 @@ impl JsProcedure {
|
||||
)
|
||||
.await?
|
||||
.read_only_effects()
|
||||
.run_action(name, input);
|
||||
.run_action(name, input, self.args.clone());
|
||||
let output: ErrorValue = match timeout {
|
||||
Some(timeout_duration) => tokio::time::timeout(timeout_duration, running_action)
|
||||
.await
|
||||
@@ -145,7 +148,7 @@ fn unwrap_known_error<O: for<'de> Deserialize<'de>>(
|
||||
|
||||
#[tokio::test]
|
||||
async fn js_action_execute() {
|
||||
let js_action = JsProcedure {};
|
||||
let js_action = JsProcedure { args: vec![] };
|
||||
let path: PathBuf = "test/js_action_execute/"
|
||||
.parse::<PathBuf>()
|
||||
.unwrap()
|
||||
@@ -200,7 +203,7 @@ async fn js_action_execute() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn js_action_execute_error() {
|
||||
let js_action = JsProcedure {};
|
||||
let js_action = JsProcedure { args: vec![] };
|
||||
let path: PathBuf = "test/js_action_execute/"
|
||||
.parse::<PathBuf>()
|
||||
.unwrap()
|
||||
@@ -244,7 +247,7 @@ async fn js_action_execute_error() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn js_action_fetch() {
|
||||
let js_action = JsProcedure {};
|
||||
let js_action = JsProcedure { args: vec![] };
|
||||
let path: PathBuf = "test/js_action_execute/"
|
||||
.parse::<PathBuf>()
|
||||
.unwrap()
|
||||
@@ -285,3 +288,92 @@ async fn js_action_fetch() {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn js_action_var_arg() {
|
||||
let js_action = JsProcedure {
|
||||
args: vec![42.into()],
|
||||
};
|
||||
let path: PathBuf = "test/js_action_execute/"
|
||||
.parse::<PathBuf>()
|
||||
.unwrap()
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
let package_id = "test-package".parse().unwrap();
|
||||
let package_version: Version = "0.3.0.3".parse().unwrap();
|
||||
let name = ProcedureName::Action("js-action-var-arg".parse().unwrap());
|
||||
let volumes: Volumes = serde_json::from_value(serde_json::json!({
|
||||
"main": {
|
||||
"type": "data"
|
||||
},
|
||||
"compat": {
|
||||
"type": "assets"
|
||||
},
|
||||
"filebrowser" :{
|
||||
"package-id": "filebrowser",
|
||||
"path": "data",
|
||||
"readonly": true,
|
||||
"type": "pointer",
|
||||
"volume-id": "main",
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
let input: Option<serde_json::Value> = None;
|
||||
let timeout = Some(Duration::from_secs(10));
|
||||
js_action
|
||||
.execute::<serde_json::Value, serde_json::Value>(
|
||||
&path,
|
||||
&package_id,
|
||||
&package_version,
|
||||
name,
|
||||
&volumes,
|
||||
input,
|
||||
timeout,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn js_action_test_rename() {
|
||||
let js_action = JsProcedure { args: vec![] };
|
||||
let path: PathBuf = "test/js_action_execute/"
|
||||
.parse::<PathBuf>()
|
||||
.unwrap()
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
let package_id = "test-package".parse().unwrap();
|
||||
let package_version: Version = "0.3.0.3".parse().unwrap();
|
||||
let name = ProcedureName::Action("test-rename".parse().unwrap());
|
||||
let volumes: Volumes = serde_json::from_value(serde_json::json!({
|
||||
"main": {
|
||||
"type": "data"
|
||||
},
|
||||
"compat": {
|
||||
"type": "assets"
|
||||
},
|
||||
"filebrowser" :{
|
||||
"package-id": "filebrowser",
|
||||
"path": "data",
|
||||
"readonly": true,
|
||||
"type": "pointer",
|
||||
"volume-id": "main",
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
let input: Option<serde_json::Value> = None;
|
||||
let timeout = Some(Duration::from_secs(10));
|
||||
js_action
|
||||
.execute::<serde_json::Value, serde_json::Value>(
|
||||
&path,
|
||||
&package_id,
|
||||
&package_version,
|
||||
name,
|
||||
&volumes,
|
||||
input,
|
||||
timeout,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -40,12 +40,15 @@ impl PackageProcedure {
|
||||
#[instrument]
|
||||
pub fn validate(
|
||||
&self,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
expected_io: bool,
|
||||
) -> Result<(), color_eyre::eyre::Report> {
|
||||
match self {
|
||||
PackageProcedure::Docker(action) => action.validate(volumes, image_ids, expected_io),
|
||||
PackageProcedure::Docker(action) => {
|
||||
action.validate(eos_version, volumes, image_ids, expected_io)
|
||||
}
|
||||
|
||||
#[cfg(feature = "js_engine")]
|
||||
PackageProcedure::Script(action) => action.validate(volumes),
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::procedure::ProcedureName;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::{Error, ErrorKind};
|
||||
|
||||
pub fn display_properties(response: Value, _: &ArgMatches<'_>) {
|
||||
pub fn display_properties(response: Value, _: &ArgMatches) {
|
||||
println!("{}", response);
|
||||
}
|
||||
|
||||
|
||||
@@ -153,23 +153,26 @@ impl<R: AsyncRead + AsyncSeek + Unpin> S9pkReader<R> {
|
||||
man.actions
|
||||
.0
|
||||
.iter()
|
||||
.map(|(_, action)| action.validate(&man.volumes, &validated_image_ids))
|
||||
.map(|(_, action)| {
|
||||
action.validate(&man.eos_version, &man.volumes, &validated_image_ids)
|
||||
})
|
||||
.collect::<Result<(), Error>>()?;
|
||||
man.backup.validate(&man.volumes, &validated_image_ids)?;
|
||||
man.backup
|
||||
.validate(&man.eos_version, &man.volumes, &validated_image_ids)?;
|
||||
if let Some(cfg) = &man.config {
|
||||
cfg.validate(&man.volumes, &validated_image_ids)?;
|
||||
cfg.validate(&man.eos_version, &man.volumes, &validated_image_ids)?;
|
||||
}
|
||||
man.health_checks
|
||||
.validate(&man.volumes, &validated_image_ids)?;
|
||||
.validate(&man.eos_version, &man.volumes, &validated_image_ids)?;
|
||||
man.interfaces.validate()?;
|
||||
man.main
|
||||
.validate(&man.volumes, &validated_image_ids, false)
|
||||
.validate(&man.eos_version, &man.volumes, &validated_image_ids, false)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Main"))?;
|
||||
man.migrations
|
||||
.validate(&man.volumes, &validated_image_ids)?;
|
||||
.validate(&man.eos_version, &man.volumes, &validated_image_ids)?;
|
||||
if let Some(props) = &man.properties {
|
||||
props
|
||||
.validate(&man.volumes, &validated_image_ids, true)
|
||||
.validate(&man.eos_version, &man.volumes, &validated_image_ids, true)
|
||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Properties"))?;
|
||||
}
|
||||
man.volumes.validate(&man.interfaces)?;
|
||||
|
||||
@@ -6,10 +6,10 @@ use rpc_toolkit::command;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::disk::main::export;
|
||||
use crate::init::SYSTEM_REBUILD_PATH;
|
||||
use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH};
|
||||
use crate::sound::SHUTDOWN;
|
||||
use crate::util::{display_none, Invoke};
|
||||
use crate::Error;
|
||||
use crate::{Error, ErrorKind};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Shutdown {
|
||||
@@ -54,23 +54,24 @@ impl Shutdown {
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
}
|
||||
if let Err(e) = SHUTDOWN.play().await {
|
||||
tracing::error!("Error Playing Shutdown Song: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
if self.restart {
|
||||
if let Err(e) = SHUTDOWN.play().await {
|
||||
tracing::error!("Error Playing Shutdown Song: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
} else {
|
||||
tokio::fs::write(STANDBY_MODE_PATH, "").await.unwrap();
|
||||
Command::new("sync")
|
||||
.invoke(ErrorKind::Filesystem)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
drop(rt);
|
||||
if self.restart {
|
||||
Command::new("reboot").spawn().unwrap().wait().unwrap();
|
||||
} else {
|
||||
Command::new("shutdown")
|
||||
.arg("-h")
|
||||
.arg("now")
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
if !self.restart {
|
||||
std::fs::write(STANDBY_MODE_PATH, "").unwrap();
|
||||
}
|
||||
Command::new("reboot").spawn().unwrap().wait().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ pub async fn delete(#[context] ctx: RpcContext, #[arg] fingerprint: String) -> R
|
||||
}
|
||||
}
|
||||
|
||||
fn display_all_ssh_keys(all: Vec<SshKeyResponse>, matches: &ArgMatches<'_>) {
|
||||
fn display_all_ssh_keys(all: Vec<SshKeyResponse>, matches: &ArgMatches) {
|
||||
use prettytable::*;
|
||||
|
||||
if matches.is_present("format") {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::id::{ ImageId};
|
||||
use crate::id::ImageId;
|
||||
use crate::procedure::{NoOutput, PackageProcedure, ProcedureName};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::Duration;
|
||||
@@ -19,11 +19,16 @@ pub use models::HealthCheckId;
|
||||
pub struct HealthChecks(pub BTreeMap<HealthCheckId, HealthCheck>);
|
||||
impl HealthChecks {
|
||||
#[instrument]
|
||||
pub fn validate(&self, volumes: &Volumes, image_ids: &BTreeSet<ImageId>) -> Result<(), Error> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
) -> Result<(), Error> {
|
||||
for (_, check) in &self.0 {
|
||||
check
|
||||
.implementation
|
||||
.validate(&volumes, image_ids, false)
|
||||
.validate(eos_version, &volumes, image_ids, false)
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::ValidateS9pk,
|
||||
|
||||
@@ -63,6 +63,16 @@ impl MainStatus {
|
||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => (),
|
||||
}
|
||||
}
|
||||
pub fn started(&self) -> Option<DateTime<Utc>> {
|
||||
match self {
|
||||
MainStatus::Running { started, .. } => Some(*started),
|
||||
MainStatus::BackingUp { started, .. } => *started,
|
||||
MainStatus::Stopped => None,
|
||||
MainStatus::Restarting => None,
|
||||
MainStatus::Stopping => None,
|
||||
MainStatus::Starting { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MainStatusModel {
|
||||
pub fn started(self) -> Model<Option<DateTime<Utc>>> {
|
||||
|
||||
@@ -76,7 +76,7 @@ pub enum UpdateResult {
|
||||
Updating,
|
||||
}
|
||||
|
||||
fn display_update_result(status: WithRevision<UpdateResult>, _: &ArgMatches<'_>) {
|
||||
fn display_update_result(status: WithRevision<UpdateResult>, _: &ArgMatches) {
|
||||
match status.response {
|
||||
UpdateResult::Updating => {
|
||||
println!("Updating...");
|
||||
@@ -139,7 +139,7 @@ async fn maybe_do_update(
|
||||
"{}/eos/v0/latest?eos-version={}&arch={}",
|
||||
marketplace_url,
|
||||
Current::new().semver(),
|
||||
platforms::TARGET_ARCH,
|
||||
&*crate::ARCH,
|
||||
))
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?
|
||||
@@ -156,7 +156,7 @@ async fn maybe_do_update(
|
||||
.version()
|
||||
.get_mut(&mut db)
|
||||
.await?;
|
||||
if &latest_version <= ¤t_version {
|
||||
if &latest_version < ¤t_version {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut tx = db.begin().await?;
|
||||
@@ -304,7 +304,7 @@ impl std::fmt::Display for EosUrl {
|
||||
self.base,
|
||||
self.version,
|
||||
Current::new().semver(),
|
||||
platforms::TARGET_ARCH,
|
||||
&*crate::ARCH,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -356,7 +356,12 @@ async fn write_stream_to_label<Db: DbHandle>(
|
||||
pin!(stream_download);
|
||||
let mut downloaded = 0;
|
||||
let mut last_progress_update = Instant::now();
|
||||
while let Some(Ok(item)) = stream_download.next().await {
|
||||
while let Some(item) = stream_download
|
||||
.next()
|
||||
.await
|
||||
.transpose()
|
||||
.with_kind(ErrorKind::Network)?
|
||||
{
|
||||
file.write_all(&item)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
@@ -374,6 +379,7 @@ async fn write_stream_to_label<Db: DbHandle>(
|
||||
}
|
||||
file.flush().await.with_kind(ErrorKind::Filesystem)?;
|
||||
file.shutdown().await.with_kind(ErrorKind::Filesystem)?;
|
||||
file.sync_all().await.with_kind(ErrorKind::Filesystem)?;
|
||||
drop(file);
|
||||
Ok(hasher.finalize().to_vec())
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ use async_trait::async_trait;
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::{self, eyre};
|
||||
use fd_lock_rs::FdLock;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::FutureExt;
|
||||
use helpers::canonicalize;
|
||||
pub use helpers::NonDetachingJoinHandle;
|
||||
use lazy_static::lazy_static;
|
||||
pub use models::Version;
|
||||
@@ -23,7 +22,7 @@ use tokio::sync::{Mutex, OwnedMutexGuard, RwLock};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::{Error, ResultExt as _};
|
||||
use crate::{Error, ErrorKind, ResultExt as _};
|
||||
pub mod config;
|
||||
pub mod io;
|
||||
pub mod logger;
|
||||
@@ -273,40 +272,6 @@ impl<F: FnOnce() -> T, T> Drop for GeneralGuard<F, T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn canonicalize(
|
||||
path: impl AsRef<Path> + Send + Sync,
|
||||
create_parent: bool,
|
||||
) -> Result<PathBuf, Error> {
|
||||
fn create_canonical_folder<'a>(
|
||||
path: impl AsRef<Path> + Send + Sync + 'a,
|
||||
) -> BoxFuture<'a, Result<PathBuf, Error>> {
|
||||
async move {
|
||||
let path = canonicalize(path, true).await?;
|
||||
tokio::fs::create_dir(&path)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, path.display().to_string()))?;
|
||||
Ok(path)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
let path = path.as_ref();
|
||||
if tokio::fs::metadata(path).await.is_err() {
|
||||
if let (Some(parent), Some(file_name)) = (path.parent(), path.file_name()) {
|
||||
if create_parent && tokio::fs::metadata(parent).await.is_err() {
|
||||
return Ok(create_canonical_folder(parent).await?.join(file_name));
|
||||
} else {
|
||||
return Ok(tokio::fs::canonicalize(parent)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, parent.display().to_string()))?
|
||||
.join(file_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
tokio::fs::canonicalize(&path)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, path.display().to_string()))
|
||||
}
|
||||
|
||||
pub struct FileLock(OwnedMutexGuard<()>, Option<FdLock<File>>);
|
||||
impl Drop for FileLock {
|
||||
fn drop(&mut self) {
|
||||
@@ -322,7 +287,9 @@ impl FileLock {
|
||||
static ref INTERNAL_LOCKS: Mutex<BTreeMap<PathBuf, Arc<Mutex<()>>>> =
|
||||
Mutex::new(BTreeMap::new());
|
||||
}
|
||||
let path = canonicalize(path.as_ref(), true).await?;
|
||||
let path = canonicalize(path.as_ref(), true)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
let mut internal_locks = INTERNAL_LOCKS.lock().await;
|
||||
if !internal_locks.contains_key(&path) {
|
||||
internal_locks.insert(path.clone(), Arc::new(Mutex::new(())));
|
||||
@@ -362,58 +329,3 @@ impl FileLock {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AtomicFile {
|
||||
tmp_path: PathBuf,
|
||||
path: PathBuf,
|
||||
file: File,
|
||||
}
|
||||
impl AtomicFile {
|
||||
pub async fn new(path: impl AsRef<Path> + Send + Sync) -> Result<Self, Error> {
|
||||
let path = canonicalize(&path, true).await?;
|
||||
let tmp_path = if let (Some(parent), Some(file_name)) =
|
||||
(path.parent(), path.file_name().and_then(|f| f.to_str()))
|
||||
{
|
||||
parent.join(format!(".{}.tmp", file_name))
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("invalid path: {}", path.display()),
|
||||
crate::ErrorKind::Filesystem,
|
||||
));
|
||||
};
|
||||
let file = File::create(&tmp_path)
|
||||
.await
|
||||
.with_ctx(|_| (crate::ErrorKind::Filesystem, tmp_path.display().to_string()))?;
|
||||
Ok(Self {
|
||||
tmp_path,
|
||||
path,
|
||||
file,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn save(mut self) -> Result<(), Error> {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
self.file.flush().await?;
|
||||
self.file.shutdown().await?;
|
||||
self.file.sync_all().await?;
|
||||
tokio::fs::rename(&self.tmp_path, &self.path)
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
format!("mv {} -> {}", self.tmp_path.display(), self.path.display()),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
impl std::ops::Deref for AtomicFile {
|
||||
type Target = File;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.file
|
||||
}
|
||||
}
|
||||
impl std::ops::DerefMut for AtomicFile {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.file
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,7 +412,7 @@ impl IoFormat {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_serializable<T: Serialize>(t: T, matches: &ArgMatches<'_>) {
|
||||
pub fn display_serializable<T: Serialize>(t: T, matches: &ArgMatches) {
|
||||
let format = match matches.value_of("format").map(|f| f.parse()) {
|
||||
Some(Ok(f)) => f,
|
||||
Some(Err(_)) => {
|
||||
@@ -428,7 +428,7 @@ pub fn display_serializable<T: Serialize>(t: T, matches: &ArgMatches<'_>) {
|
||||
|
||||
pub fn parse_stdin_deserializable<T: for<'de> Deserialize<'de>>(
|
||||
stdin: &mut std::io::Stdin,
|
||||
matches: &ArgMatches<'_>,
|
||||
matches: &ArgMatches,
|
||||
) -> Result<T, Error> {
|
||||
let format = match matches.value_of("format").map(|f| f.parse()) {
|
||||
Some(Ok(f)) => f,
|
||||
|
||||
@@ -12,8 +12,9 @@ mod v0_3_0_1;
|
||||
mod v0_3_0_2;
|
||||
mod v0_3_0_3;
|
||||
mod v0_3_1;
|
||||
mod v0_3_1_1;
|
||||
|
||||
pub type Current = v0_3_1::Version;
|
||||
pub type Current = v0_3_1_1::Version;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
@@ -23,6 +24,7 @@ enum Version {
|
||||
V0_3_0_2(Wrapper<v0_3_0_2::Version>),
|
||||
V0_3_0_3(Wrapper<v0_3_0_3::Version>),
|
||||
V0_3_1(Wrapper<v0_3_1::Version>),
|
||||
V0_3_1_1(Wrapper<v0_3_1_1::Version>),
|
||||
Other(emver::Version),
|
||||
}
|
||||
|
||||
@@ -43,6 +45,7 @@ impl Version {
|
||||
Version::V0_3_0_2(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_0_3(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_1(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_1_1(Wrapper(x)) => x.semver(),
|
||||
Version::Other(x) => x.clone(),
|
||||
}
|
||||
}
|
||||
@@ -94,10 +97,18 @@ where
|
||||
receipts: &InitReceipts,
|
||||
) -> Result<(), Error> {
|
||||
let previous = Self::Previous::new();
|
||||
if version.semver() != previous.semver() {
|
||||
if version.semver() < previous.semver() {
|
||||
previous
|
||||
.migrate_from_unchecked(version, db, receipts)
|
||||
.await?;
|
||||
} else if version.semver() > previous.semver() {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"NO PATH FROM {}, THIS IS LIKELY A MISTAKE IN THE VERSION DEFINITION",
|
||||
version.semver()
|
||||
),
|
||||
crate::ErrorKind::MigrationFailed,
|
||||
));
|
||||
}
|
||||
tracing::info!("{} -> {}", previous.semver(), self.semver(),);
|
||||
self.up(db).await?;
|
||||
@@ -114,10 +125,18 @@ where
|
||||
tracing::info!("{} -> {}", self.semver(), previous.semver(),);
|
||||
self.down(db).await?;
|
||||
previous.commit(db, receipts).await?;
|
||||
if version.semver() != previous.semver() {
|
||||
if version.semver() < previous.semver() {
|
||||
previous
|
||||
.rollback_to_unchecked(version, db, receipts)
|
||||
.await?;
|
||||
} else if version.semver() > previous.semver() {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"NO PATH TO {}, THIS IS LIKELY A MISTAKE IN THE VERSION DEFINITION",
|
||||
version.semver()
|
||||
),
|
||||
crate::ErrorKind::MigrationFailed,
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -158,6 +177,7 @@ pub async fn init<Db: DbHandle>(
|
||||
Version::V0_3_0_2(v) => v.0.migrate_to(&Current::new(), db, receipts).await?,
|
||||
Version::V0_3_0_3(v) => v.0.migrate_to(&Current::new(), db, receipts).await?,
|
||||
Version::V0_3_1(v) => v.0.migrate_to(&Current::new(), db, receipts).await?,
|
||||
Version::V0_3_1_1(v) => v.0.migrate_to(&Current::new(), db, receipts).await?,
|
||||
Version::Other(_) => {
|
||||
return Err(Error::new(
|
||||
eyre!("Cannot downgrade"),
|
||||
@@ -194,6 +214,7 @@ mod tests {
|
||||
Just(Version::V0_3_0_2(Wrapper(v0_3_0_2::Version::new()))),
|
||||
Just(Version::V0_3_0_3(Wrapper(v0_3_0_3::Version::new()))),
|
||||
Just(Version::V0_3_1(Wrapper(v0_3_1::Version::new()))),
|
||||
Just(Version::V0_3_1_1(Wrapper(v0_3_1_1::Version::new()))),
|
||||
em_version().prop_map(Version::Other),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::*;
|
||||
use super::{v0_3_0::V0_3_0_COMPAT, *};
|
||||
|
||||
const V0_3_1: emver::Version = emver::Version::new(0, 3, 1, 0);
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref V0_3_1_COMPAT: VersionRange = VersionRange::Conj(
|
||||
Box::new(VersionRange::Anchor(
|
||||
emver::GTE,
|
||||
emver::Version::new(0, 3, 0, 0),
|
||||
)),
|
||||
Box::new(VersionRange::Anchor(emver::LTE, Current::new().semver())),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Version;
|
||||
@@ -25,7 +16,7 @@ impl VersionT for Version {
|
||||
V0_3_1
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_1_COMPAT
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db) -> Result<(), Error> {
|
||||
Ok(())
|
||||
|
||||
27
backend/src/version/v0_3_1_1.rs
Normal file
27
backend/src/version/v0_3_1_1.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::{v0_3_0::V0_3_0_COMPAT, *};
|
||||
|
||||
const V0_3_1_1: emver::Version = emver::Version::new(0, 3, 1, 1);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_1::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_1_1
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -750,6 +750,64 @@ export const action = {
|
||||
}
|
||||
})
|
||||
assert(((await secondResponse.json()).json.message === message), "Body should be parsed from response");
|
||||
return {
|
||||
result: {
|
||||
copyable: false,
|
||||
message: "Done",
|
||||
version: "0",
|
||||
qr: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async 'js-action-var-arg'(_effects, _input, testInput) {
|
||||
|
||||
assert(testInput == 42, "Input should be passed in");
|
||||
return {
|
||||
result: {
|
||||
copyable: false,
|
||||
message: "Done",
|
||||
version: "0",
|
||||
qr: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
async 'test-rename'(effects, _input) {
|
||||
|
||||
await effects.writeFile({
|
||||
volumeId: 'main',
|
||||
path: 'test-rename.txt',
|
||||
toWrite: 'Hello World',
|
||||
});
|
||||
await effects.rename({
|
||||
srcVolume: 'main',
|
||||
srcPath: 'test-rename.txt',
|
||||
dstVolume: 'main',
|
||||
dstPath: 'test-rename-2.txt',
|
||||
});
|
||||
|
||||
const readIn = await effects.readFile({
|
||||
volumeId: 'main',
|
||||
path: 'test-rename-2.txt',
|
||||
});
|
||||
assert(readIn === 'Hello World', "Contents should be the same");
|
||||
|
||||
await effects.removeFile({
|
||||
path: "test-rename-2.txt",
|
||||
volumeId: "main",
|
||||
});
|
||||
|
||||
try{
|
||||
|
||||
await effects.removeFile({
|
||||
path: "test-rename.txt",
|
||||
volumeId: "main",
|
||||
});
|
||||
assert(false, "Should not be able to remove file that doesn't exist");
|
||||
}
|
||||
catch (_){}
|
||||
|
||||
|
||||
return {
|
||||
result: {
|
||||
copyable: false,
|
||||
|
||||
21
build-cargo-dep.sh
Executable file
21
build-cargo-dep.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
shopt -s expand_aliases
|
||||
|
||||
if [ "$0" != "./build-cargo-dep.sh" ]; then
|
||||
>&2 echo "Must be run from embassy-os directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USE_TTY=
|
||||
if tty -s; then
|
||||
USE_TTY="-it"
|
||||
fi
|
||||
|
||||
mkdir -p cargo-deps
|
||||
alias 'rust-arm64-builder'='docker run $USE_TTY --rm -v "$HOME/.cargo/registry":/root/.cargo/registry -v "$(pwd)"/cargo-deps:/home/rust/src -P start9/rust-arm-cross:aarch64'
|
||||
|
||||
rust-arm64-builder cargo install "$1" --target-dir /home/rust/src
|
||||
sudo chown -R $USER cargo-deps
|
||||
sudo chown -R $USER ~/.cargo
|
||||
@@ -8,6 +8,7 @@ Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/local/bin/initialization.sh
|
||||
RemainAfterExit=true
|
||||
StandardOutput=append:/var/log/initialization.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -92,6 +92,16 @@ ControlPort 9051
|
||||
CookieAuthentication 1
|
||||
EOF
|
||||
|
||||
cat << EOF > /etc/NetworkManager/NetworkManager.conf
|
||||
[main]
|
||||
plugins=ifupdown,keyfile
|
||||
dns=none
|
||||
rc-manager=unmanaged
|
||||
|
||||
[ifupdown]
|
||||
managed=false
|
||||
EOF
|
||||
|
||||
# enable embassyd dns server
|
||||
systemctl enable systemd-resolved
|
||||
sed -i '/\(^\|#\)DNS=/c\DNS=127.0.0.1' /etc/systemd/resolved.conf
|
||||
@@ -114,6 +124,10 @@ sed -i 's/usb-storage.quirks=152d:0562:u,14cd:121c:u,0781:cfcb:u //g' /boot/cmdl
|
||||
cp /boot/cmdline.txt /boot/cmdline.txt.orig
|
||||
sed -i 's/^/usb-storage.quirks=152d:0562:u,14cd:121c:u,0781:cfcb:u /g' /boot/cmdline.txt
|
||||
|
||||
# making that *sudo docker stats* command fulfil its purpose by displaying all metrics
|
||||
sed -i 's/rootwait quiet.*/rootwait cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory quiet/g' /boot/cmdline.txt
|
||||
|
||||
systemctl disable nc-broadcast.service
|
||||
systemctl disable initialization.service
|
||||
sudo systemctl restart NetworkManager
|
||||
|
||||
|
||||
13
build/nc-broadcast.service
Normal file
13
build/nc-broadcast.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Writes initialization logs to network
|
||||
Requires=initialization.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/local/bin/nc-broadcast --input=/var/log/initialization.log --tee 0.0.0.0:8080
|
||||
RemainAfterExit=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -3,4 +3,4 @@
|
||||
set -e
|
||||
|
||||
# Use fdisk to create DOS partition table with 4 primary partitions, set 1 as bootable, write, and quite
|
||||
(echo o; echo x; echo i; echo "0xcb15ae4d"; echo r; echo n; echo p; echo 1; echo 2048; echo 526335; echo t; echo c; echo n; echo p; echo 2; echo 526336; echo 1050623; echo t; echo 2; echo c; echo n; echo p; echo 3; echo 1050624; echo 16083455; echo n; echo p; echo 16083456; echo 31116287; echo a; echo 1; echo w) | sudo fdisk ${OUTPUT_DEVICE}
|
||||
(echo o; echo x; echo i; echo "0xcb15ae4d"; echo r; echo n; echo p; echo 1; echo 2048; echo 526335; echo t; echo c; echo n; echo p; echo 2; echo 526336; echo 1050623; echo t; echo 2; echo c; echo n; echo p; echo 3; echo 1050624; echo 16083455; echo n; echo p; echo 16083456; echo 31116287; echo a; echo 1; echo w) | sudo fdisk ${OUTPUT_DEVICE} > /dev/null
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
#cloud-config
|
||||
|
||||
# This is the user-data configuration file for cloud-init. By default this sets
|
||||
# up an initial user called "ubuntu" with password "ubuntu", which must be
|
||||
# changed at first login. However, many additional actions can be initiated on
|
||||
# first boot from this file. The cloud-init documentation has more details:
|
||||
#
|
||||
# https://cloudinit.readthedocs.io/
|
||||
#
|
||||
# Please note that the YAML format employed by this file is sensitive to
|
||||
# differences in whitespace; if you are editing this file in an editor (like
|
||||
# Notepad) which uses literal tabs, take care to only use spaces for
|
||||
# indentation. See the following link for more details:
|
||||
#
|
||||
# https://en.wikipedia.org/wiki/YAML
|
||||
#
|
||||
# Some additional examples are provided in comments below the default
|
||||
# configuration.
|
||||
|
||||
# On first boot, set the (default) ubuntu user's password to "ubuntu" and
|
||||
# expire user passwords
|
||||
#chpasswd:
|
||||
# expire: true
|
||||
# list:
|
||||
# - ubuntu:ubuntu
|
||||
|
||||
# Enable password authentication with the SSH daemon
|
||||
#ssh_pwauth: true
|
||||
|
||||
## On first boot, use ssh-import-id to give the specific users SSH access to
|
||||
## the default user
|
||||
#ssh_import_id:
|
||||
#- lp:my_launchpad_username
|
||||
#- gh:my_github_username
|
||||
|
||||
## Add users and groups to the system, and import keys with the ssh-import-id
|
||||
## utility
|
||||
#groups:
|
||||
#- robot: [robot]
|
||||
#- robotics: [robot]
|
||||
#- pi
|
||||
#
|
||||
#users:
|
||||
#- default
|
||||
#- name: robot
|
||||
# gecos: Mr. Robot
|
||||
# primary_group: robot
|
||||
# groups: users
|
||||
# ssh_import_id: foobar
|
||||
# lock_passwd: false
|
||||
# passwd: $5$hkui88$nvZgIle31cNpryjRfO9uArF7DYiBcWEnjqq7L1AQNN3
|
||||
|
||||
## Update apt database and upgrade packages on first boot
|
||||
#package_update: true
|
||||
#package_upgrade: true
|
||||
|
||||
## Install additional packages on first boot
|
||||
#packages:
|
||||
#- pwgen
|
||||
#- pastebinit
|
||||
#- [libpython2.7, 2.7.3-0ubuntu3.1]
|
||||
|
||||
## Write arbitrary files to the file-system (including binaries!)
|
||||
#write_files:
|
||||
#- path: /etc/default/keyboard
|
||||
# content: |
|
||||
# # KEYBOARD configuration file
|
||||
# # Consult the keyboard(5) manual page.
|
||||
# XKBMODEL="pc105"
|
||||
# XKBLAYOUT="gb"
|
||||
# XKBVARIANT=""
|
||||
# XKBOPTIONS="ctrl: nocaps"
|
||||
# permissions: '0644'
|
||||
# owner: root:root
|
||||
#- encoding: gzip
|
||||
# path: /usr/bin/hello
|
||||
# content: !!binary |
|
||||
# H4sIAIDb/U8C/1NW1E/KzNMvzuBKTc7IV8hIzcnJVyjPL8pJ4QIA6N+MVxsAAAA=
|
||||
# owner: root:root
|
||||
# permissions: '0755'
|
||||
|
||||
## Run arbitrary commands at rc.local like time
|
||||
#runcmd:
|
||||
#- [ ls, -l, / ]
|
||||
#- [ sh, -xc, "echo $(date) ': hello world!'" ]
|
||||
#- [ wget, "http://ubuntu.com", -O, /run/mydir/index.html ]
|
||||
@@ -1,86 +0,0 @@
|
||||
#cloud-config
|
||||
|
||||
# This is the user-data configuration file for cloud-init. By default this sets
|
||||
# up an initial user called "ubuntu" with password "ubuntu", which must be
|
||||
# changed at first login. However, many additional actions can be initiated on
|
||||
# first boot from this file. The cloud-init documentation has more details:
|
||||
#
|
||||
# https://cloudinit.readthedocs.io/
|
||||
#
|
||||
# Please note that the YAML format employed by this file is sensitive to
|
||||
# differences in whitespace; if you are editing this file in an editor (like
|
||||
# Notepad) which uses literal tabs, take care to only use spaces for
|
||||
# indentation. See the following link for more details:
|
||||
#
|
||||
# https://en.wikipedia.org/wiki/YAML
|
||||
#
|
||||
# Some additional examples are provided in comments below the default
|
||||
# configuration.
|
||||
|
||||
# On first boot, set the (default) ubuntu user's password to "ubuntu" and
|
||||
# expire user passwords
|
||||
chpasswd:
|
||||
expire: true
|
||||
list:
|
||||
- ubuntu:ubuntu
|
||||
|
||||
# Enable password authentication with the SSH daemon
|
||||
ssh_pwauth: true
|
||||
|
||||
## On first boot, use ssh-import-id to give the specific users SSH access to
|
||||
## the default user
|
||||
#ssh_import_id:
|
||||
#- lp:my_launchpad_username
|
||||
#- gh:my_github_username
|
||||
|
||||
## Add users and groups to the system, and import keys with the ssh-import-id
|
||||
## utility
|
||||
#groups:
|
||||
#- robot: [robot]
|
||||
#- robotics: [robot]
|
||||
#- pi
|
||||
#
|
||||
#users:
|
||||
#- default
|
||||
#- name: robot
|
||||
# gecos: Mr. Robot
|
||||
# primary_group: robot
|
||||
# groups: users
|
||||
# ssh_import_id: foobar
|
||||
# lock_passwd: false
|
||||
# passwd: $5$hkui88$nvZgIle31cNpryjRfO9uArF7DYiBcWEnjqq7L1AQNN3
|
||||
|
||||
## Update apt database and upgrade packages on first boot
|
||||
#package_update: true
|
||||
#package_upgrade: true
|
||||
|
||||
## Install additional packages on first boot
|
||||
#packages:
|
||||
#- pwgen
|
||||
#- pastebinit
|
||||
#- [libpython2.7, 2.7.3-0ubuntu3.1]
|
||||
|
||||
## Write arbitrary files to the file-system (including binaries!)
|
||||
#write_files:
|
||||
#- path: /etc/default/keyboard
|
||||
# content: |
|
||||
# # KEYBOARD configuration file
|
||||
# # Consult the keyboard(5) manual page.
|
||||
# XKBMODEL="pc105"
|
||||
# XKBLAYOUT="gb"
|
||||
# XKBVARIANT=""
|
||||
# XKBOPTIONS="ctrl: nocaps"
|
||||
# permissions: '0644'
|
||||
# owner: root:root
|
||||
#- encoding: gzip
|
||||
# path: /usr/bin/hello
|
||||
# content: !!binary |
|
||||
# H4sIAIDb/U8C/1NW1E/KzNMvzuBKTc7IV8hIzcnJVyjPL8pJ4QIA6N+MVxsAAAA=
|
||||
# owner: root:root
|
||||
# permissions: '0755'
|
||||
|
||||
## Run arbitrary commands at rc.local like time
|
||||
#runcmd:
|
||||
#- [ ls, -l, / ]
|
||||
#- [ sh, -xc, "echo $(date) ': hello world!'" ]
|
||||
#- [ wget, "http://ubuntu.com", -O, /run/mydir/index.html ]
|
||||
@@ -11,7 +11,7 @@ function partition_for () {
|
||||
}
|
||||
|
||||
# Write contents of LOOPDEV (Ubuntu image) to sd card and make filesystems, then detach the loop device
|
||||
echo USING $LOOPDEV TO IMAGE $OUTPUT_DEVICE
|
||||
echo USING $LOOPDEV TO IMAGE $OUTPUT_DEVICE WITH ENVIRONMENT $ENVIRONMENT
|
||||
sudo dd if=${LOOPDEV}p1 of=`partition_for ${OUTPUT_DEVICE} 1` bs=1M iflag=fullblock oflag=direct conv=fsync status=progress
|
||||
sudo mkfs.vfat -F 32 `partition_for ${OUTPUT_DEVICE} 2`
|
||||
sudo dd if=${LOOPDEV}p2 of=`partition_for ${OUTPUT_DEVICE} 3` bs=1M iflag=fullblock oflag=direct conv=fsync status=progress
|
||||
@@ -29,18 +29,11 @@ sudo e2label `partition_for ${OUTPUT_DEVICE} 4` blue
|
||||
mkdir -p /tmp/eos-mnt
|
||||
sudo mount `partition_for ${OUTPUT_DEVICE} 1` /tmp/eos-mnt
|
||||
|
||||
if [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
|
||||
sudo cp build/user-data-dev /tmp/eos-mnt/user-data
|
||||
else
|
||||
sudo cp build/user-data /tmp/eos-mnt/user-data
|
||||
fi
|
||||
|
||||
sudo sed -i 's/PARTUUID=cb15ae4d-02/PARTUUID=cb15ae4d-03/g' /tmp/eos-mnt/cmdline.txt
|
||||
sudo sed -i 's/ init=\/usr\/lib\/raspi-config\/init_resize.sh//g' /tmp/eos-mnt/cmdline.txt
|
||||
sudo sed -i 's/quiet.*/cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory quiet/g' /tmp/eos-mnt/cmdline.txt
|
||||
|
||||
cat /tmp/eos-mnt/config.txt | grep -v "dtoverlay=" | sudo tee /tmp/eos-mnt/config.txt.tmp
|
||||
echo "dtoverlay=pwm-2chan,disable-bt" | sudo tee -a /tmp/eos-mnt/config.txt.tmp
|
||||
cat /tmp/eos-mnt/config.txt | grep -v "dtoverlay=" | sudo tee /tmp/eos-mnt/config.txt.tmp > /dev/null
|
||||
echo "dtoverlay=pwm-2chan,disable-bt" | sudo tee -a /tmp/eos-mnt/config.txt.tmp > /dev/null
|
||||
sudo mv /tmp/eos-mnt/config.txt.tmp /tmp/eos-mnt/config.txt
|
||||
sudo touch /tmp/eos-mnt/ssh
|
||||
|
||||
@@ -52,10 +45,17 @@ sudo umount /tmp/eos-mnt
|
||||
|
||||
sudo mount `partition_for ${OUTPUT_DEVICE} 3` /tmp/eos-mnt
|
||||
|
||||
sudo mkdir /tmp/eos-mnt/media/boot-rw
|
||||
sudo mkdir /tmp/eos-mnt/embassy-os
|
||||
sudo mkdir /tmp/eos-mnt/media/boot-rw
|
||||
sudo mkdir /tmp/eos-mnt/embassy-os
|
||||
sudo mkdir /tmp/eos-mnt/etc/embassy
|
||||
sudo cp ENVIRONMENT.txt /tmp/eos-mnt/etc/embassy
|
||||
sudo cp GIT_HASH.txt /tmp/eos-mnt/etc/embassy
|
||||
sudo cp build/fstab /tmp/eos-mnt/etc/fstab
|
||||
sudo cp build/journald.conf /tmp/eos-mnt/etc/systemd/journald.conf
|
||||
|
||||
# copy over cargo dependencies
|
||||
sudo cp cargo-deps/aarch64-unknown-linux-gnu/release/nc-broadcast /tmp/eos-mnt/usr/local/bin
|
||||
|
||||
# Enter the backend directory, copy over the built EmbassyOS binaries and systemd services, edit the nginx config, then create the .ssh directory
|
||||
cd backend/
|
||||
|
||||
@@ -98,5 +98,7 @@ fi
|
||||
|
||||
sudo cp ./build/initialization.service /tmp/eos-mnt/etc/systemd/system/initialization.service
|
||||
sudo ln -s /etc/systemd/system/initialization.service /tmp/eos-mnt/etc/systemd/system/multi-user.target.wants/initialization.service
|
||||
sudo cp ./build/nc-broadcast.service /tmp/eos-mnt/etc/systemd/system/nc-broadcast.service
|
||||
sudo ln -s /etc/systemd/system/nc-broadcast.service /tmp/eos-mnt/etc/systemd/system/multi-user.target.wants/nc-broadcast.service
|
||||
|
||||
sudo umount /tmp/eos-mnt
|
||||
|
||||
7
check-environment.sh
Executable file
7
check-environment.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
if ! [ -f ./ENVIRONMENT.txt ] || [ "$(cat ./ENVIRONMENT.txt)" != "$ENVIRONMENT" ]; then
|
||||
echo -n "$ENVIRONMENT" > ./ENVIRONMENT.txt
|
||||
fi
|
||||
|
||||
echo -n ./ENVIRONMENT.txt
|
||||
9
check-git-hash.sh
Executable file
9
check-git-hash.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
GIT_HASH="$(git describe --always --abbrev=40 --dirty=-modified)"
|
||||
|
||||
if ! [ -f ./GIT_HASH.txt ] || [ "$(cat ./GIT_HASH.txt)" != "$GIT_HASH" ]; then
|
||||
echo -n "$GIT_HASH" > ./GIT_HASH.txt
|
||||
fi
|
||||
|
||||
echo -n ./GIT_HASH.txt
|
||||
BIN
deno_core.tar.gz
BIN
deno_core.tar.gz
Binary file not shown.
@@ -1,7 +1,6 @@
|
||||
// @ts-check
|
||||
const fs = require('fs')
|
||||
const childProcess = require('child_process')
|
||||
const { env } = require('process')
|
||||
|
||||
const gitHash = String(
|
||||
childProcess.execSync('git describe --always --abbrev=40 --dirty=-modified'),
|
||||
@@ -9,8 +8,5 @@ const gitHash = String(
|
||||
|
||||
const origConfig = require('./config.json')
|
||||
|
||||
const registries = require('./registries.json')
|
||||
|
||||
origConfig['gitHash'] = gitHash
|
||||
origConfig.ui['marketplace'] = registries.prod
|
||||
fs.writeFileSync('./config.json', JSON.stringify(origConfig, null, 2))
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"skipStartupAlerts": true
|
||||
},
|
||||
"marketplace": {
|
||||
"url": "https://beta-registry-0-3.start9labs.com/",
|
||||
"name": "Embassy Marketplace"
|
||||
"url": "https://registry.start9.com/",
|
||||
"name": "Start9 Marketplace"
|
||||
}
|
||||
},
|
||||
"gitHash": ""
|
||||
|
||||
8
frontend/lint-staged.config.js
Normal file
8
frontend/lint-staged.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
'**/*.{js,ts,html,md,json}': 'prettier --write',
|
||||
'*.ts': 'tslint --fix',
|
||||
'projects/ui/**/*.ts': () => 'npm run check:ui',
|
||||
'projects/shared/**/*.ts': () => 'npm run check:shared',
|
||||
'projects/diagnostic-ui/**/*.ts': () => 'npm run check:diagnostic-ui',
|
||||
'projects/setup-wizard/**/*.ts': () => 'npm run check:setup-wizard',
|
||||
}
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "embassy-os",
|
||||
"version": "0.3.1",
|
||||
"version": "0.3.1.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "embassy-os",
|
||||
"version": "0.3.1",
|
||||
"version": "0.3.1.1",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^13.3.0",
|
||||
"@angular/common": "^13.3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "embassy-os",
|
||||
"version": "0.3.1",
|
||||
"version": "0.3.1.1",
|
||||
"author": "Start9 Labs, Inc",
|
||||
"homepage": "https://start9.com/",
|
||||
"scripts": {
|
||||
@@ -13,7 +13,7 @@
|
||||
"build:deps": "rm -rf .angular/cache && cd ../patch-db/client && npm ci && npm run build",
|
||||
"build:diagnostic-ui": "ng run diagnostic-ui:build",
|
||||
"build:setup-wizard": "ng run setup-wizard:build",
|
||||
"build:ui": "ng run ui:build && tsc projects/ui/postprocess.ts && node projects/ui/postprocess.js && git log | head -n1 > dist/ui/git-hash.txt",
|
||||
"build:ui": "ng run ui:build",
|
||||
"build:all": "npm run build:deps && npm run build:diagnostic-ui && npm run build:setup-wizard && npm run build:ui",
|
||||
"start:diagnostic-ui": "npm run-script build-config && ionic serve --project diagnostic-ui --host 0.0.0.0",
|
||||
"start:setup-wizard": "npm run-script build-config && ionic serve --project setup-wizard --host 0.0.0.0",
|
||||
@@ -84,11 +84,7 @@
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged && npm run check"
|
||||
"pre-commit": "lint-staged --concurrent false"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{js,ts,html,md,less,json}": "prettier --write",
|
||||
"*.ts": "tslint --fix"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,13 @@ var convert = new Convert({
|
||||
styleUrls: ['./logs.page.scss'],
|
||||
})
|
||||
export class LogsPage {
|
||||
@ViewChild(IonContent) private content: IonContent
|
||||
@ViewChild(IonContent) private content?: IonContent
|
||||
loading = true
|
||||
loadingMore = false
|
||||
logs: string
|
||||
needInfinite = true
|
||||
startCursor: string
|
||||
endCursor: string
|
||||
startCursor?: string
|
||||
endCursor?: string
|
||||
limit = 200
|
||||
scrollToBottomButton = false
|
||||
isOnBottom = true
|
||||
|
||||
constructor(private readonly api: ApiService) {}
|
||||
@@ -52,7 +50,7 @@ export class LogsPage {
|
||||
|
||||
// scroll down
|
||||
scrollBy(0, afterContainerHeight - beforeContainerHeight)
|
||||
this.content.scrollToPoint(
|
||||
this.content?.scrollToPoint(
|
||||
0,
|
||||
afterContainerHeight - beforeContainerHeight,
|
||||
)
|
||||
@@ -117,7 +115,7 @@ export class LogsPage {
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
this.content.scrollToBottom(500)
|
||||
this.content?.scrollToBottom(500)
|
||||
}
|
||||
|
||||
async loadData(e: any): Promise<void> {
|
||||
|
||||
@@ -11,9 +11,7 @@ export class HttpService {
|
||||
async rpcRequest<T>(options: RPCOptions): Promise<T> {
|
||||
const res = await this.httpRequest<RPCResponse<T>>(options)
|
||||
if (isRpcError(res)) throw new RpcError(res.error)
|
||||
if (isRpcSuccess(res)) return res.result
|
||||
|
||||
throw new Error('Unknown RPC response')
|
||||
return res.result
|
||||
}
|
||||
|
||||
async httpRequest<T>(body: RPCOptions): Promise<T> {
|
||||
@@ -31,13 +29,7 @@ export class HttpService {
|
||||
function isRpcError<Error, Result>(
|
||||
arg: { error: Error } | { result: Result },
|
||||
): arg is { error: Error } {
|
||||
return !!(arg as any).error
|
||||
}
|
||||
|
||||
function isRpcSuccess<Error, Result>(
|
||||
arg: { error: Error } | { result: Result },
|
||||
): arg is { result: Result } {
|
||||
return !!(arg as any).result
|
||||
return (arg as any).error !== undefined
|
||||
}
|
||||
|
||||
export interface RPCOptions {
|
||||
|
||||
@@ -9,5 +9,5 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg'
|
||||
})
|
||||
export class ItemComponent {
|
||||
@Input()
|
||||
pkg: MarketplacePkg
|
||||
pkg!: MarketplacePkg
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { NgModule } from '@angular/core'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
@@ -6,7 +7,7 @@ import { SharedPipesModule } from '@start9labs/shared'
|
||||
import { ItemComponent } from './item.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [IonicModule, RouterModule, SharedPipesModule],
|
||||
imports: [CommonModule, IonicModule, RouterModule, SharedPipesModule],
|
||||
declarations: [ItemComponent],
|
||||
exports: [ItemComponent],
|
||||
})
|
||||
|
||||
@@ -10,5 +10,5 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg'
|
||||
})
|
||||
export class AboutComponent {
|
||||
@Input()
|
||||
pkg: MarketplacePkg
|
||||
pkg!: MarketplacePkg
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg'
|
||||
})
|
||||
export class AdditionalComponent {
|
||||
@Input()
|
||||
pkg: MarketplacePkg
|
||||
pkg!: MarketplacePkg
|
||||
|
||||
@Output()
|
||||
version = new EventEmitter<string>()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { NgModule } from '@angular/core'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { MarkdownModule } from '@start9labs/shared'
|
||||
@@ -5,7 +6,7 @@ import { MarkdownModule } from '@start9labs/shared'
|
||||
import { AdditionalComponent } from './additional.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [IonicModule, MarkdownModule],
|
||||
imports: [CommonModule, IonicModule, MarkdownModule],
|
||||
declarations: [AdditionalComponent],
|
||||
exports: [AdditionalComponent],
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg'
|
||||
})
|
||||
export class DependenciesComponent {
|
||||
@Input()
|
||||
pkg: MarketplacePkg
|
||||
pkg!: MarketplacePkg
|
||||
|
||||
getImg(key: string): string {
|
||||
return 'data:image/png;base64,' + this.pkg['dependency-metadata'][key].icon
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
<div class="text">
|
||||
<h1 class="title">{{ pkg.manifest.title }}</h1>
|
||||
<p class="version">{{ pkg.manifest.version | displayEmver }}</p>
|
||||
<!-- @TODO remove conditional when registry code deployed. published-at will be required -->
|
||||
<p *ngIf="pkg['published-at']" class="published">
|
||||
Released: {{ pkg['published-at'] | date: 'medium' }}
|
||||
</p>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,11 +18,16 @@
|
||||
}
|
||||
|
||||
.version {
|
||||
padding: 4px 0 12px 0;
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.published {
|
||||
margin: 0;
|
||||
padding: 4px 0 12px 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
.logo {
|
||||
width: 140px;
|
||||
|
||||
@@ -10,5 +10,5 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg'
|
||||
})
|
||||
export class PackageComponent {
|
||||
@Input()
|
||||
pkg: MarketplacePkg
|
||||
pkg!: MarketplacePkg
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { NgModule } from '@angular/core'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { EmverPipesModule, SharedPipesModule } from '@start9labs/shared'
|
||||
@@ -5,7 +6,7 @@ import { EmverPipesModule, SharedPipesModule } from '@start9labs/shared'
|
||||
import { PackageComponent } from './package.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [IonicModule, SharedPipesModule, EmverPipesModule],
|
||||
imports: [CommonModule, IonicModule, SharedPipesModule, EmverPipesModule],
|
||||
declarations: [PackageComponent],
|
||||
exports: [PackageComponent],
|
||||
})
|
||||
|
||||
@@ -14,4 +14,5 @@ export interface MarketplacePkg {
|
||||
icon: Url
|
||||
}
|
||||
}
|
||||
'published-at': string
|
||||
}
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>
|
||||
{{ !!storageDrive ? 'Set Password' : 'Unlock Drive' }}
|
||||
{{ storageDrive ? 'Set Password' : 'Unlock Drive' }}
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div style="padding: 8px 24px;">
|
||||
<div style="padding-bottom: 16px;">
|
||||
<ng-container *ngIf="!!storageDrive">
|
||||
<p>Choose a password for your Embassy. <i>Make it good. Write it down.</i></p>
|
||||
<p style="color: var(--ion-color-warning);">Losing your password can result in total loss of data.</p>
|
||||
</ng-container>
|
||||
<p *ngIf="!storageDrive">Enter the password that was used to encrypt this drive.</p>
|
||||
<div style="padding: 8px 24px">
|
||||
<div style="padding-bottom: 16px">
|
||||
<ng-template #choose>
|
||||
<p>
|
||||
Choose a password for your Embassy.
|
||||
<i>Make it good. Write it down.</i>
|
||||
</p>
|
||||
<p style="color: var(--ion-color-warning)">
|
||||
Losing your password can result in total loss of data.
|
||||
</p>
|
||||
</ng-template>
|
||||
<p *ngIf="!storageDrive else choose">
|
||||
Enter the password that was used to encrypt this drive.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form (ngSubmit)="!!storageDrive ? submitPw() : verifyPw()">
|
||||
<form (ngSubmit)="storageDrive ? submitPw() : verifyPw()">
|
||||
<p>Password</p>
|
||||
<ion-item [class]="pwError ? 'error-border' : password && !!storageDrive ? 'success-border' : ''">
|
||||
<ion-item
|
||||
[class]="pwError ? 'error-border' : password && storageDrive ? 'success-border' : ''"
|
||||
>
|
||||
<ion-input
|
||||
#focusInput
|
||||
[(ngModel)]="password"
|
||||
@@ -29,15 +38,23 @@
|
||||
maxlength="64"
|
||||
></ion-input>
|
||||
<ion-button fill="clear" color="light" (click)="unmasked1 = !unmasked1">
|
||||
<ion-icon slot="icon-only" [name]="unmasked1 ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
[name]="unmasked1 ? 'eye-off-outline' : 'eye-outline'"
|
||||
size="small"
|
||||
></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<div style="height: 16px;">
|
||||
<p style="color: var(--ion-color-danger); font-size: x-small;">{{ pwError }}</p>
|
||||
<div style="height: 16px">
|
||||
<p style="color: var(--ion-color-danger); font-size: x-small">
|
||||
{{ pwError }}
|
||||
</p>
|
||||
</div>
|
||||
<ng-container *ngIf="!!storageDrive">
|
||||
<ng-container *ngIf="storageDrive">
|
||||
<p>Confirm Password</p>
|
||||
<ion-item [class]="verError ? 'error-border' : passwordVer ? 'success-border' : ''">
|
||||
<ion-item
|
||||
[class]="verError ? 'error-border' : passwordVer ? 'success-border' : ''"
|
||||
>
|
||||
<ion-input
|
||||
[(ngModel)]="passwordVer"
|
||||
[ngModelOptions]="{'standalone': true}"
|
||||
@@ -46,12 +63,22 @@
|
||||
maxlength="64"
|
||||
placeholder="Retype Password"
|
||||
></ion-input>
|
||||
<ion-button fill="clear" color="light" (click)="unmasked2 = !unmasked2">
|
||||
<ion-icon slot="icon-only" [name]="unmasked2 ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
|
||||
<ion-button
|
||||
fill="clear"
|
||||
color="light"
|
||||
(click)="unmasked2 = !unmasked2"
|
||||
>
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
[name]="unmasked2 ? 'eye-off-outline' : 'eye-outline'"
|
||||
size="small"
|
||||
></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<div style="height: 16px;">
|
||||
<p style="color: var(--ion-color-danger); font-size: x-small;">{{ verError }}</p>
|
||||
<div style="height: 16px">
|
||||
<p style="color: var(--ion-color-danger); font-size: x-small">
|
||||
{{ verError }}
|
||||
</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
<input type="submit" style="display: none" />
|
||||
@@ -61,12 +88,24 @@
|
||||
|
||||
<ion-footer>
|
||||
<ion-toolbar>
|
||||
<ion-button class="ion-padding-end" slot="end" color="dark" fill="clear" (click)="cancel()">
|
||||
<ion-button
|
||||
class="ion-padding-end"
|
||||
slot="end"
|
||||
color="dark"
|
||||
fill="clear"
|
||||
(click)="cancel()"
|
||||
>
|
||||
Cancel
|
||||
</ion-button>
|
||||
<ion-button class="ion-padding-end" slot="end" color="dark" fill="clear" strong="true" (click)="!!storageDrive ? submitPw() : verifyPw()">
|
||||
{{ !!storageDrive ? 'Finish' : 'Unlock' }}
|
||||
<ion-button
|
||||
class="ion-padding-end"
|
||||
slot="end"
|
||||
color="dark"
|
||||
fill="clear"
|
||||
strong="true"
|
||||
(click)="storageDrive ? submitPw() : verifyPw()"
|
||||
>
|
||||
{{ storageDrive ? 'Finish' : 'Unlock' }}
|
||||
</ion-button>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ import * as argon2 from '@start9labs/argon2'
|
||||
styleUrls: ['password.page.scss'],
|
||||
})
|
||||
export class PasswordPage {
|
||||
@ViewChild('focusInput') elem: IonInput
|
||||
@Input() target: CifsBackupTarget | DiskBackupTarget
|
||||
@Input() storageDrive: DiskInfo
|
||||
@ViewChild('focusInput') elem?: IonInput
|
||||
@Input() target?: CifsBackupTarget | DiskBackupTarget
|
||||
@Input() storageDrive?: DiskInfo
|
||||
|
||||
pwError = ''
|
||||
password = ''
|
||||
@@ -28,7 +28,7 @@ export class PasswordPage {
|
||||
constructor(private modalController: ModalController) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => this.elem.setFocus(), 400)
|
||||
setTimeout(() => this.elem?.setFocus(), 400)
|
||||
}
|
||||
|
||||
async verifyPw() {
|
||||
@@ -36,7 +36,7 @@ export class PasswordPage {
|
||||
this.pwError = 'No recovery target' // unreachable
|
||||
|
||||
try {
|
||||
const passwordHash = this.target['embassy-os']?.['password-hash'] || ''
|
||||
const passwordHash = this.target!['embassy-os']?.['password-hash'] || ''
|
||||
|
||||
argon2.verify(passwordHash, this.password)
|
||||
this.modalController.dismiss({ password: this.password }, 'success')
|
||||
|
||||
@@ -9,8 +9,8 @@ import { HttpService } from 'src/app/services/api/http.service'
|
||||
styleUrls: ['prod-key-modal.page.scss'],
|
||||
})
|
||||
export class ProdKeyModal {
|
||||
@ViewChild('focusInput') elem: IonInput
|
||||
@Input() target: DiskBackupTarget
|
||||
@ViewChild('focusInput') elem?: IonInput
|
||||
@Input() target!: DiskBackupTarget
|
||||
|
||||
error = ''
|
||||
productKey = ''
|
||||
@@ -24,7 +24,7 @@ export class ProdKeyModal {
|
||||
) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => this.elem.setFocus(), 400)
|
||||
setTimeout(() => this.elem?.setFocus(), 400)
|
||||
}
|
||||
|
||||
async verifyProductKey() {
|
||||
|
||||
@@ -10,24 +10,24 @@ import { StateService } from 'src/app/services/state.service'
|
||||
styleUrls: ['product-key.page.scss'],
|
||||
})
|
||||
export class ProductKeyPage {
|
||||
@ViewChild('focusInput') elem: IonInput
|
||||
productKey: string
|
||||
error: string
|
||||
@ViewChild('focusInput') elem?: IonInput
|
||||
productKey = ''
|
||||
error = ''
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly stateService: StateService,
|
||||
private readonly apiService: ApiService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly httpService: HttpService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
ionViewDidEnter () {
|
||||
setTimeout(() => this.elem.setFocus(), 400)
|
||||
ionViewDidEnter() {
|
||||
setTimeout(() => this.elem?.setFocus(), 400)
|
||||
}
|
||||
|
||||
async submit () {
|
||||
if (!this.productKey) return this.error = 'Must enter product key'
|
||||
async submit() {
|
||||
if (!this.productKey) return (this.error = 'Must enter product key')
|
||||
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Verifying Product Key',
|
||||
@@ -50,4 +50,3 @@ export class ProductKeyPage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export class RecoverPage {
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errorToastService: ErrorToastService,
|
||||
public readonly stateService: StateService,
|
||||
private readonly stateService: StateService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -243,8 +243,8 @@ export class RecoverPage {
|
||||
styleUrls: ['./recover.page.scss'],
|
||||
})
|
||||
export class DriveStatusComponent {
|
||||
@Input() hasValidBackup: boolean
|
||||
@Input() is02x: boolean
|
||||
@Input() hasValidBackup!: boolean
|
||||
@Input() is02x!: boolean
|
||||
}
|
||||
|
||||
interface MappedDisk {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<ion-card-content>
|
||||
<br />
|
||||
<ng-template
|
||||
[ngIf]="stateService.recoverySource && stateService.recoverySource.type === 'disk'"
|
||||
[ngIf]="recoverySource && recoverySource.type === 'disk'"
|
||||
>
|
||||
<h2>You can now safely unplug your backup drive.</h2>
|
||||
</ng-template>
|
||||
@@ -53,15 +53,13 @@
|
||||
<ion-item lines="none" color="dark">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<code
|
||||
><ion-text color="light"
|
||||
>{{ stateService.torAddress }}</ion-text
|
||||
></code
|
||||
><ion-text color="light">{{ torAddress }}</ion-text></code
|
||||
>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
color="light"
|
||||
fill="clear"
|
||||
(click)="copy(stateService.torAddress)"
|
||||
(click)="copy(torAddress)"
|
||||
>
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
@@ -133,15 +131,13 @@
|
||||
<ion-item lines="none" color="dark">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<code
|
||||
><ion-text color="light"
|
||||
>{{ stateService.lanAddress }}</ion-text
|
||||
></code
|
||||
><ion-text color="light">{{ lanAddress }}</ion-text></code
|
||||
>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
color="light"
|
||||
fill="clear"
|
||||
(click)="copy(stateService.lanAddress)"
|
||||
(click)="copy(lanAddress)"
|
||||
>
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
|
||||
@@ -16,9 +16,21 @@ export class SuccessPage {
|
||||
constructor(
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly errCtrl: ErrorToastService,
|
||||
public readonly stateService: StateService,
|
||||
private readonly stateService: StateService,
|
||||
) {}
|
||||
|
||||
get recoverySource() {
|
||||
return this.stateService.recoverySource
|
||||
}
|
||||
|
||||
get torAddress() {
|
||||
return this.stateService.torAddress
|
||||
}
|
||||
|
||||
get lanAddress() {
|
||||
return this.stateService.lanAddress
|
||||
}
|
||||
|
||||
async ngAfterViewInit() {
|
||||
try {
|
||||
await this.stateService.completeEmbassy()
|
||||
|
||||
@@ -42,9 +42,7 @@ export class HttpService {
|
||||
throw new RpcError(res.error)
|
||||
}
|
||||
|
||||
if (isRpcSuccess(res)) return res.result
|
||||
|
||||
throw new Error('Unknown RPC response')
|
||||
return res.result
|
||||
}
|
||||
|
||||
async encryptedHttpRequest<T>(httpOpts: {
|
||||
@@ -132,13 +130,7 @@ class EncryptionError {
|
||||
function isRpcError<Error, Result>(
|
||||
arg: { error: Error } | { result: Result },
|
||||
): arg is { error: Error } {
|
||||
return !!(arg as any).error
|
||||
}
|
||||
|
||||
function isRpcSuccess<Error, Result>(
|
||||
arg: { error: Error } | { result: Result },
|
||||
): arg is { result: Result } {
|
||||
return !!(arg as any).result
|
||||
return (arg as any).error !== undefined
|
||||
}
|
||||
|
||||
export enum Method {
|
||||
|
||||
@@ -11,26 +11,26 @@ import { pauseFor, ErrorToastService } from '@start9labs/shared'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class StateService {
|
||||
hasProductKey: boolean
|
||||
isMigrating: boolean
|
||||
hasProductKey = false
|
||||
isMigrating = false
|
||||
|
||||
polling = false
|
||||
embassyLoaded = false
|
||||
|
||||
recoverySource: CifsRecoverySource | DiskRecoverySource
|
||||
recoverySource?: CifsRecoverySource | DiskRecoverySource
|
||||
recoveryPassword?: string
|
||||
|
||||
dataTransferProgress: {
|
||||
dataTransferProgress?: {
|
||||
bytesTransferred: number
|
||||
totalBytes: number
|
||||
complete: boolean
|
||||
} | null
|
||||
}
|
||||
dataProgress = 0
|
||||
dataCompletionSubject = new BehaviorSubject(false)
|
||||
|
||||
torAddress: string
|
||||
lanAddress: string
|
||||
cert: string
|
||||
torAddress = ''
|
||||
lanAddress = ''
|
||||
cert = ''
|
||||
|
||||
constructor(
|
||||
private readonly apiService: ApiService,
|
||||
|
||||
@@ -11,8 +11,8 @@ import { getErrorMessage } from '../../services/error-toast.service'
|
||||
styleUrls: ['./markdown.component.scss'],
|
||||
})
|
||||
export class MarkdownComponent {
|
||||
@Input() content?: string | Observable<string>
|
||||
@Input() title = ''
|
||||
@Input() content!: string | Observable<string>
|
||||
@Input() title!: string
|
||||
|
||||
private readonly data$ = defer(() =>
|
||||
isObservable(this.content) ? this.content : of(this.content),
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
|
||||
name: 'trustUrl',
|
||||
})
|
||||
export class TrustUrlPipe implements PipeTransform {
|
||||
constructor(public readonly sanitizer: DomSanitizer) {}
|
||||
constructor(private readonly sanitizer: DomSanitizer) {}
|
||||
|
||||
transform(base64Icon: string): SafeResourceUrl {
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(base64Icon)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { parse } from 'node-html-parser'
|
||||
import * as fs from 'fs'
|
||||
|
||||
let index = fs.readFileSync('./dist/ui/index.html').toString('utf-8')
|
||||
|
||||
const root = parse(index)
|
||||
for (let elem of root.querySelectorAll('link')) {
|
||||
if (elem.getAttribute('rel') === 'stylesheet') {
|
||||
const sheet = fs
|
||||
.readFileSync('./dist/ui/' + elem.getAttribute('href'))
|
||||
.toString('utf-8')
|
||||
index = index.replace(elem.toString(), '<style>' + sheet + '</style>')
|
||||
}
|
||||
}
|
||||
fs.writeFileSync('./dist/ui/index.html', index)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user