Compare commits

...

38 Commits

Author SHA1 Message Date
Aiden McClelland
d5f7e15dfb fix typo (#1702) 2022-07-26 17:32:37 -06:00
Aiden McClelland
7bf7b1e71e NO_KEY for CI images (#1700) 2022-07-26 17:32:28 -06:00
Matt Hill
7b17498722 set Matt as default assignee (#1697) 2022-07-26 15:12:31 -06:00
Aiden McClelland
3473633e43 sync blockdev after update (#1694) 2022-07-26 10:34:23 -06:00
Aiden McClelland
f455b8a007 ask for sudo password immediately during make (#1693) 2022-07-26 10:32:32 -06:00
Aiden McClelland
daabba12d3 honor shutdown from diagnostic ui (#1692) 2022-07-25 20:21:15 -06:00
Matt Hill
61864d082f messaging for restart, shutdown, rebuild (#1691)
* messaging for restart, shutdown, rebuild

* fix typo

* better messaging
2022-07-25 15:28:53 -06:00
Aiden McClelland
a7cd1e0ce6 sync data to fs before shutdown (#1690) 2022-07-25 15:23:40 -06:00
Matt Hill
0dd6d3a500 marketplace published at for service (#1689)
* at published timestamp to marketplace package show

* add todo
2022-07-25 12:47:50 -06:00
Aiden McClelland
bdb906bf26 add marketplace_url to backup metadata for service (#1688) 2022-07-25 12:43:40 -06:00
Aiden McClelland
61da050fe8 only validate mounts for inject if eos >=0.3.1.1 (#1686)
only validate mounts for inject if `>=0.3.1.1`
2022-07-25 12:20:24 -06:00
Matt Hill
83fe391796 replace bang with question mark in html (#1683) 2022-07-25 12:05:44 -06:00
Aiden McClelland
37657fa6ad issue notification when individual package restore fails (#1685) 2022-07-25 12:02:41 -06:00
Aiden McClelland
908a945b95 allow falsey rpc response (#1680)
* allow falsey rpc response

* better check for rpc error and remove extra function

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
2022-07-25 10:16:04 -06:00
Aiden McClelland
36c720227f allow server.update to update to current version (#1679) 2022-07-22 14:10:09 -06:00
J M
c22c80d3b0 feat: atomic writing (#1673)
* feat: atomic writing

* Apply suggestions from code review

* clean up temp files on error

Co-authored-by: Aiden McClelland <me@drbonez.dev>
2022-07-22 14:08:49 -06:00
Aiden McClelland
15af827cbc add standby mode (#1671)
* add standby mode

* fix standby mode to go before drive mount
2022-07-22 13:13:49 -06:00
Matt Hill
4a54c7ca87 draft releases notes for 0311 (#1677) 2022-07-22 11:17:18 -06:00
Alex Inkin
7b8a0eadf3 chore: enable strict mode (#1569)
* chore: enable strict mode

* refactor: remove sync data access from PatchDbService

* launchable even when no LAN url

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
2022-07-22 09:51:08 -06:00
Aiden McClelland
9a01a0df8e refactor build process (#1675)
* add nc-broadcast to view initialization.sh logs

* include stderr

* refactor build

* add frontend/config.json as frontend dependency

* fix nc-broadcast

* always run all workflows

* address dependabot alerts

* fix build caching

* remove registries.json

* more efficient build
2022-07-21 15:18:44 -06:00
Aiden McClelland
ea2d77f536 lower log level for docker deser fallback message (#1672) 2022-07-21 12:13:46 -06:00
Aiden McClelland
e29003539b trust local ca (#1670) 2022-07-21 12:11:47 -06:00
Lucy C
97bdb2dd64 run build checks only when relevant FE changes (#1664) 2022-07-20 20:22:26 -06:00
Aiden McClelland
40d446ba32 fix migration, add logging (#1674)
* fix migration, add logging

* change stack overflow to runtime error
2022-07-20 16:00:25 -06:00
J M
5fa743755d feat: Make the rename effect (#1669)
* feat: Make the rename effect

* chore: Change to dst and src

* chore: update the remove file to use dst src
2022-07-20 13:42:54 -06:00
J M
0f027fefb8 chore: Update to have the new version 0.3.1.1 (#1668) 2022-07-19 10:03:14 -06:00
Matt Hill
56acb3f281 Mask chars beyond 16 (#1666)
fixes #1662
2022-07-18 18:31:53 -06:00
Lucy C
5268185604 add readme to system-images folder (#1665) 2022-07-18 15:48:42 -06:00
J M
635c3627c9 feat: Variable args (#1667)
* feat: Variable args

* chore: Make the assert error message not wrong
2022-07-18 15:46:32 -06:00
Chris Guida
009f7ddf84 sdk: don't allow mounts in inject actions (#1653) 2022-07-18 12:26:00 -06:00
J M
4526618c32 fix: Resolve fighting with NM (#1660) 2022-07-18 09:44:33 -06:00
Matt Hill
6dfd46197d handle case where selected union enum is invalid after migration (#1658)
* handle case where selected union enum is invalid after migration

* revert necessary ternary and fix types

Co-authored-by: Lucy Cifferello <12953208+elvece@users.noreply.github.com>
2022-07-17 13:42:36 -06:00
Aiden McClelland
778471d3cc Update product.yaml (#1638) 2022-07-13 11:15:45 -06:00
Aiden McClelland
bbcf2990f6 fix build (#1639) 2022-07-13 11:14:50 -06:00
Aiden McClelland
ac30ab223b return correct error on failed os download (#1636) 2022-07-12 12:48:18 -06:00
J M
50e7b479b5 Fix/receipts health (#1616)
* release lock on update progress (#1614)

* chore: remove the receipt

* chore: Remove the receipt

Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
2022-07-12 12:48:01 -06:00
Aiden McClelland
1367428499 update backend dependencies (#1637) 2022-07-12 12:18:12 -06:00
Mariusz Kogen
e5de91cbe5 🐋 docker stats fix (#1630)
* entry obsolete...

* 🐋 docker stats fix
2022-07-11 18:18:22 -06:00
232 changed files with 3418 additions and 2898 deletions

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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
View File

@@ -12,3 +12,6 @@ deploy_web.sh
deploy_web.sh
secrets.db
.vscode/
/cargo-deps/**/*
ENVIRONMENT.txt
GIT_HASH.txt

3
.gitmodules vendored
View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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());

View File

@@ -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()

View File

@@ -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);

View File

@@ -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") {

View File

@@ -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)?

View File

@@ -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),
)

View File

@@ -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),
),

View File

@@ -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
}

View File

@@ -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(())
}

View File

@@ -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(

View File

@@ -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, .. } => {

View File

@@ -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(),

View File

@@ -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")

View File

@@ -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") {

View File

@@ -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(())
}

View File

@@ -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(())
}

View File

@@ -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(())
}

View File

@@ -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)?;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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 { .. }))

View File

@@ -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(

View File

@@ -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(())
}

View File

@@ -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()?;

View File

@@ -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") {

View File

@@ -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),

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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),

View File

@@ -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);
}

View File

@@ -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)?;

View File

@@ -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();
}
}

View File

@@ -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") {

View File

@@ -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,

View File

@@ -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>>> {

View File

@@ -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 <= &current_version {
if &latest_version < &current_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())
}

View File

@@ -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
}
}

View 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,

View File

@@ -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),
]
}

View File

@@ -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(())

View 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(())
}
}

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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 ]

View File

@@ -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 ]

View File

@@ -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
View 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
View 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

Binary file not shown.

View File

@@ -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))

View File

@@ -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": ""

View 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',
}

View File

@@ -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",

View File

@@ -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"
}
}

View File

@@ -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> {

View File

@@ -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 {

View File

@@ -9,5 +9,5 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg'
})
export class ItemComponent {
@Input()
pkg: MarketplacePkg
pkg!: MarketplacePkg
}

View File

@@ -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],
})

View File

@@ -10,5 +10,5 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg'
})
export class AboutComponent {
@Input()
pkg: MarketplacePkg
pkg!: MarketplacePkg
}

View File

@@ -18,7 +18,7 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg'
})
export class AdditionalComponent {
@Input()
pkg: MarketplacePkg
pkg!: MarketplacePkg
@Output()
version = new EventEmitter<string>()

View File

@@ -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],
})

View File

@@ -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

View File

@@ -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>

View File

@@ -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;

View File

@@ -10,5 +10,5 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg'
})
export class PackageComponent {
@Input()
pkg: MarketplacePkg
pkg!: MarketplacePkg
}

View File

@@ -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],
})

View File

@@ -14,4 +14,5 @@ export interface MarketplacePkg {
icon: Url
}
}
'published-at': string
}

View File

@@ -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>

View File

@@ -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')

View File

@@ -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() {

View File

@@ -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 {
}
}
}

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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),

View File

@@ -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)

View File

@@ -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