mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
4
.github/workflows/startos-iso.yaml
vendored
4
.github/workflows/startos-iso.yaml
vendored
@@ -41,11 +41,11 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
- next/*
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- next
|
- next/*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODEJS_VERSION: "18.15.0"
|
NODEJS_VERSION: "18.15.0"
|
||||||
|
|||||||
31
.github/workflows/test.yaml
vendored
Normal file
31
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Automated Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next/*
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODEJS_VERSION: "18.15.0"
|
||||||
|
ENVIRONMENT: dev-unstable
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Run Automated Tests
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODEJS_VERSION }}
|
||||||
|
|
||||||
|
- name: Build And Run Tests
|
||||||
|
run: make test
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -28,3 +28,4 @@ secrets.db
|
|||||||
/dpkg-workdir
|
/dpkg-workdir
|
||||||
/compiled.tar
|
/compiled.tar
|
||||||
/compiled-*.tar
|
/compiled-*.tar
|
||||||
|
/firmware
|
||||||
18
Makefile
18
Makefile
@@ -8,14 +8,15 @@ ARCH := $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo aarch64; else ec
|
|||||||
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
IMAGE_TYPE=$(shell if [ "$(PLATFORM)" = raspberrypi ]; then echo img; else echo iso; fi)
|
||||||
BINS := core/target/$(ARCH)-unknown-linux-gnu/release/startbox core/target/aarch64-unknown-linux-musl/release/container-init core/target/x86_64-unknown-linux-musl/release/container-init
|
BINS := core/target/$(ARCH)-unknown-linux-gnu/release/startbox core/target/aarch64-unknown-linux-musl/release/container-init core/target/x86_64-unknown-linux-musl/release/container-init
|
||||||
WEB_UIS := web/dist/raw/ui web/dist/raw/setup-wizard web/dist/raw/diagnostic-ui web/dist/raw/install-wizard
|
WEB_UIS := web/dist/raw/ui web/dist/raw/setup-wizard web/dist/raw/diagnostic-ui web/dist/raw/install-wizard
|
||||||
BUILD_SRC := $(shell git ls-files build) build/lib/depends build/lib/conflicts
|
FIRMWARE_ROMS := ./firmware/$(PLATFORM) $(shell jq --raw-output '.[] | select(.platform[] | contains("$(PLATFORM)")) | "./firmware/$(PLATFORM)/" + .id + ".rom.gz"' build/lib/firmware.json)
|
||||||
|
BUILD_SRC := $(shell git ls-files build) build/lib/depends build/lib/conflicts $(FIRMWARE_ROMS)
|
||||||
DEBIAN_SRC := $(shell git ls-files debian/)
|
DEBIAN_SRC := $(shell git ls-files debian/)
|
||||||
IMAGE_RECIPE_SRC := $(shell git ls-files image-recipe/)
|
IMAGE_RECIPE_SRC := $(shell git ls-files image-recipe/)
|
||||||
STARTD_SRC := core/startos/startd.service $(BUILD_SRC)
|
STARTD_SRC := core/startos/startd.service $(BUILD_SRC)
|
||||||
COMPAT_SRC := $(shell git ls-files system-images/compat/)
|
COMPAT_SRC := $(shell git ls-files system-images/compat/)
|
||||||
UTILS_SRC := $(shell git ls-files system-images/utils/)
|
UTILS_SRC := $(shell git ls-files system-images/utils/)
|
||||||
BINFMT_SRC := $(shell git ls-files system-images/binfmt/)
|
BINFMT_SRC := $(shell git ls-files system-images/binfmt/)
|
||||||
CORE_SRC := $(shell git ls-files core) $(shell git ls-files --recurse-submodules patch-db) web/dist/static
|
CORE_SRC := $(shell git ls-files core) $(shell git ls-files --recurse-submodules patch-db) web/dist/static web/patchdb-ui-seed.json $(GIT_HASH_FILE)
|
||||||
WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules web/config.json patch-db/client/dist web/patchdb-ui-seed.json
|
WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules web/config.json patch-db/client/dist web/patchdb-ui-seed.json
|
||||||
WEB_UI_SRC := $(shell git ls-files web/projects/ui)
|
WEB_UI_SRC := $(shell git ls-files web/projects/ui)
|
||||||
WEB_SETUP_WIZARD_SRC := $(shell git ls-files web/projects/setup-wizard)
|
WEB_SETUP_WIZARD_SRC := $(shell git ls-files web/projects/setup-wizard)
|
||||||
@@ -48,7 +49,7 @@ endif
|
|||||||
|
|
||||||
.DELETE_ON_ERROR:
|
.DELETE_ON_ERROR:
|
||||||
|
|
||||||
.PHONY: all metadata install clean format sdk snapshots uis ui reflash deb $(IMAGE_TYPE) squashfs sudo wormhole
|
.PHONY: all metadata install clean format sdk snapshots uis ui reflash deb $(IMAGE_TYPE) squashfs sudo wormhole test
|
||||||
|
|
||||||
all: $(ALL_TARGETS)
|
all: $(ALL_TARGETS)
|
||||||
|
|
||||||
@@ -72,6 +73,7 @@ clean:
|
|||||||
rm -rf dpkg-workdir
|
rm -rf dpkg-workdir
|
||||||
rm -rf image-recipe/deb
|
rm -rf image-recipe/deb
|
||||||
rm -rf results
|
rm -rf results
|
||||||
|
rm -rf build/lib/firmware
|
||||||
rm -f ENVIRONMENT.txt
|
rm -f ENVIRONMENT.txt
|
||||||
rm -f PLATFORM.txt
|
rm -f PLATFORM.txt
|
||||||
rm -f GIT_HASH.txt
|
rm -f GIT_HASH.txt
|
||||||
@@ -80,6 +82,9 @@ clean:
|
|||||||
format:
|
format:
|
||||||
cd core && cargo +nightly fmt
|
cd core && cargo +nightly fmt
|
||||||
|
|
||||||
|
test: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
|
cd core && cargo build && cargo test
|
||||||
|
|
||||||
sdk:
|
sdk:
|
||||||
cd core && ./install-sdk.sh
|
cd core && ./install-sdk.sh
|
||||||
|
|
||||||
@@ -132,6 +137,8 @@ install: $(ALL_TARGETS)
|
|||||||
$(call cp,system-images/utils/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/utils.tar)
|
$(call cp,system-images/utils/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/utils.tar)
|
||||||
$(call cp,system-images/binfmt/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/binfmt.tar)
|
$(call cp,system-images/binfmt/docker-images/$(ARCH).tar,$(DESTDIR)/usr/lib/startos/system-images/binfmt.tar)
|
||||||
|
|
||||||
|
$(call cp,firmware/$(PLATFORM),$(DESTDIR)/usr/lib/startos/firmware)
|
||||||
|
|
||||||
update-overlay: $(ALL_TARGETS)
|
update-overlay: $(ALL_TARGETS)
|
||||||
@echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m"
|
@echo "\033[33m!!! THIS WILL ONLY REFLASH YOUR DEVICE IN MEMORY !!!\033[0m"
|
||||||
@echo "\033[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m"
|
@echo "\033[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m"
|
||||||
@@ -162,6 +169,9 @@ upload-ota: results/$(BASENAME).squashfs
|
|||||||
build/lib/depends build/lib/conflicts: build/dpkg-deps/*
|
build/lib/depends build/lib/conflicts: build/dpkg-deps/*
|
||||||
build/dpkg-deps/generate.sh
|
build/dpkg-deps/generate.sh
|
||||||
|
|
||||||
|
$(FIRMWARE_ROMS): build/lib/firmware.json download-firmware.sh $(PLATFORM_FILE)
|
||||||
|
./download-firmware.sh $(PLATFORM)
|
||||||
|
|
||||||
system-images/compat/docker-images/$(ARCH).tar: $(COMPAT_SRC) core/Cargo.lock
|
system-images/compat/docker-images/$(ARCH).tar: $(COMPAT_SRC) core/Cargo.lock
|
||||||
cd system-images/compat && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
cd system-images/compat && make docker-images/$(ARCH).tar && touch docker-images/$(ARCH).tar
|
||||||
|
|
||||||
@@ -175,7 +185,7 @@ snapshots: core/snapshot-creator/Cargo.toml
|
|||||||
cd core/ && ARCH=aarch64 ./build-v8-snapshot.sh
|
cd core/ && ARCH=aarch64 ./build-v8-snapshot.sh
|
||||||
cd core/ && ARCH=x86_64 ./build-v8-snapshot.sh
|
cd core/ && ARCH=x86_64 ./build-v8-snapshot.sh
|
||||||
|
|
||||||
$(BINS): $(CORE_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) web/patchdb-ui-seed.json
|
$(BINS): $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
cd core && ARCH=$(ARCH) ./build-prod.sh
|
cd core && ARCH=$(ARCH) ./build-prod.sh
|
||||||
touch $(BINS)
|
touch $(BINS)
|
||||||
|
|
||||||
|
|||||||
13
build/lib/firmware.json
Normal file
13
build/lib/firmware.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "pureboot-librem_mini_v2-basic_usb_autoboot_blob_jail-Release-28.3",
|
||||||
|
"platform": ["x86_64"],
|
||||||
|
"system-product-name": "librem_mini_v2",
|
||||||
|
"bios-version": {
|
||||||
|
"semver-prefix": "PureBoot-Release-",
|
||||||
|
"semver-range": "<28.3"
|
||||||
|
},
|
||||||
|
"url": "https://source.puri.sm/firmware/releases/-/raw/master/librem_mini_v2/custom/pureboot-librem_mini_v2-basic_usb_autoboot_blob_jail-Release-28.3.rom.gz",
|
||||||
|
"shasum": "5019bcf53f7493c7aa74f8ef680d18b5fc26ec156c705a841433aaa2fdef8f35"
|
||||||
|
}
|
||||||
|
]
|
||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
embassy-cli net dhcp update $interface
|
start-cli net dhcp update $interface
|
||||||
6
core/Cargo.lock
generated
6
core/Cargo.lock
generated
@@ -4238,6 +4238,9 @@ name = "semver"
|
|||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver-parser"
|
name = "semver-parser"
|
||||||
@@ -4921,7 +4924,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "start-os"
|
name = "start-os"
|
||||||
version = "0.3.5"
|
version = "0.3.5-rev.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
@@ -5000,6 +5003,7 @@ dependencies = [
|
|||||||
"rpc-toolkit",
|
"rpc-toolkit",
|
||||||
"rust-argon2",
|
"rust-argon2",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
|
"semver 1.0.20",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
|||||||
@@ -1201,11 +1201,11 @@ mod fns {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_is_subset() {
|
async fn test_is_subset() {
|
||||||
assert!(
|
let home = std::env::var("HOME").unwrap();
|
||||||
!is_subset("/home/drbonez", "/home/drbonez/code/fakedir/../../..")
|
let home = Path::new(&home);
|
||||||
|
assert!(!is_subset(home, &home.join("code/fakedir/../../.."))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap())
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
|
||||||
use crate::Id;
|
use crate::{Id, InvalidId};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||||
pub struct InterfaceId(Id);
|
pub struct InterfaceId(Id);
|
||||||
|
impl FromStr for InterfaceId {
|
||||||
|
type Err = InvalidId;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Self(Id::try_from(s.to_owned())?))
|
||||||
|
}
|
||||||
|
}
|
||||||
impl From<Id> for InterfaceId {
|
impl From<Id> for InterfaceId {
|
||||||
fn from(id: Id) -> Self {
|
fn from(id: Id) -> Self {
|
||||||
Self(id)
|
Self(id)
|
||||||
|
|||||||
15
core/startos/.sqlx/query-350ab82048fb4a049042e4fdbe1b8c606ca400e43e31b9a05d2937217e0f6962.json
generated
Normal file
15
core/startos/.sqlx/query-350ab82048fb4a049042e4fdbe1b8c606ca400e43e31b9a05d2937217e0f6962.json
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "DELETE FROM tor WHERE package = $1 AND interface = $2",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "350ab82048fb4a049042e4fdbe1b8c606ca400e43e31b9a05d2937217e0f6962"
|
||||||
|
}
|
||||||
12
core/startos/.sqlx/query-b81592b3a74940ab56d41537484090d45cfa4c85168a587b1a41dc5393cccea1.json
generated
Normal file
12
core/startos/.sqlx/query-b81592b3a74940ab56d41537484090d45cfa4c85168a587b1a41dc5393cccea1.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "UPDATE account SET tor_key = NULL, network_key = gen_random_bytes(32)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "b81592b3a74940ab56d41537484090d45cfa4c85168a587b1a41dc5393cccea1"
|
||||||
|
}
|
||||||
15
core/startos/.sqlx/query-dfc23b7e966c3853284753a7e934351ba0cae3825988b3e0ecd3b6781bcff524.json
generated
Normal file
15
core/startos/.sqlx/query-dfc23b7e966c3853284753a7e934351ba0cae3825988b3e0ecd3b6781bcff524.json
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "DELETE FROM network_keys WHERE package = $1 AND interface = $2",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "dfc23b7e966c3853284753a7e934351ba0cae3825988b3e0ecd3b6781bcff524"
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ keywords = [
|
|||||||
name = "start-os"
|
name = "start-os"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/Start9Labs/start-os"
|
repository = "https://github.com/Start9Labs/start-os"
|
||||||
version = "0.3.5"
|
version = "0.3.5-rev.1"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@@ -128,6 +128,7 @@ rpassword = "7.2.0"
|
|||||||
rpc-toolkit = "0.2.2"
|
rpc-toolkit = "0.2.2"
|
||||||
rust-argon2 = "2.0.0"
|
rust-argon2 = "2.0.0"
|
||||||
scopeguard = "1.1" # because avahi-sys fucks your shit up
|
scopeguard = "1.1" # because avahi-sys fucks your shit up
|
||||||
|
semver = { version = "1.0.20", features = ["serde"] }
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
serde_cbor = { package = "ciborium", version = "0.2.1" }
|
serde_cbor = { package = "ciborium", version = "0.2.1" }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
@@ -169,6 +170,9 @@ zeroize = "1.6.0"
|
|||||||
[profile.test]
|
[profile.test]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
[profile.dev.package.backtrace]
|
[profile.dev.package.backtrace]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ fn select_executable(name: &str) -> Option<fn()> {
|
|||||||
match name {
|
match name {
|
||||||
#[cfg(feature = "avahi-alias")]
|
#[cfg(feature = "avahi-alias")]
|
||||||
"avahi-alias" => Some(avahi_alias::main),
|
"avahi-alias" => Some(avahi_alias::main),
|
||||||
#[cfg(feature = "js_engine")]
|
#[cfg(feature = "js-engine")]
|
||||||
"start-deno" => Some(start_deno::main),
|
"start-deno" => Some(start_deno::main),
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
"start-cli" => Some(start_cli::main),
|
"start-cli" => Some(start_cli::main),
|
||||||
@@ -36,24 +36,14 @@ fn select_executable(name: &str) -> Option<fn()> {
|
|||||||
|
|
||||||
pub fn startbox() {
|
pub fn startbox() {
|
||||||
let args = std::env::args().take(2).collect::<Vec<_>>();
|
let args = std::env::args().take(2).collect::<Vec<_>>();
|
||||||
if let Some(x) = args
|
let executable = args
|
||||||
.get(0)
|
.get(0)
|
||||||
.and_then(|s| Path::new(&*s).file_name())
|
.and_then(|s| Path::new(&*s).file_name())
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str());
|
||||||
.and_then(|s| select_executable(&s))
|
if let Some(x) = executable.and_then(|s| select_executable(&s)) {
|
||||||
{
|
|
||||||
x()
|
|
||||||
} else if let Some(x) = args.get(1).and_then(|s| select_executable(&s)) {
|
|
||||||
x()
|
x()
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
eprintln!("unknown executable: {}", executable.unwrap_or("N/A"));
|
||||||
"unknown executable: {}",
|
|
||||||
args.get(0)
|
|
||||||
.filter(|x| &**x != "startbox")
|
|
||||||
.or_else(|| args.get(1))
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.unwrap_or("N/A")
|
|
||||||
);
|
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use serde_json::Value;
|
|||||||
|
|
||||||
use crate::procedure::js_scripts::ExecuteArgs;
|
use crate::procedure::js_scripts::ExecuteArgs;
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::util::serde::{display_serializable, parse_stdin_deserializable};
|
use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat};
|
||||||
use crate::version::{Current, VersionT};
|
use crate::version::{Current, VersionT};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
@@ -23,6 +23,9 @@ fn deno_api() -> Result<(), Error> {
|
|||||||
#[command(cli_only, display(display_serializable))]
|
#[command(cli_only, display(display_serializable))]
|
||||||
async fn execute(
|
async fn execute(
|
||||||
#[arg(stdin, parse(parse_stdin_deserializable))] arg: ExecuteArgs,
|
#[arg(stdin, parse(parse_stdin_deserializable))] arg: ExecuteArgs,
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[arg(long = "format")]
|
||||||
|
format: Option<IoFormat>,
|
||||||
) -> Result<Result<Value, (i32, String)>, Error> {
|
) -> Result<Result<Value, (i32, String)>, Error> {
|
||||||
let ExecuteArgs {
|
let ExecuteArgs {
|
||||||
procedure,
|
procedure,
|
||||||
@@ -41,6 +44,9 @@ async fn execute(
|
|||||||
#[command(cli_only, display(display_serializable))]
|
#[command(cli_only, display(display_serializable))]
|
||||||
async fn sandbox(
|
async fn sandbox(
|
||||||
#[arg(stdin, parse(parse_stdin_deserializable))] arg: ExecuteArgs,
|
#[arg(stdin, parse(parse_stdin_deserializable))] arg: ExecuteArgs,
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[arg(long = "format")]
|
||||||
|
format: Option<IoFormat>,
|
||||||
) -> Result<Result<Value, (i32, String)>, Error> {
|
) -> Result<Result<Value, (i32, String)>, Error> {
|
||||||
let ExecuteArgs {
|
let ExecuteArgs {
|
||||||
procedure,
|
procedure,
|
||||||
|
|||||||
@@ -3,48 +3,64 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use helpers::NonDetachingJoinHandle;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::rpc::RpcContextConfig;
|
use crate::context::rpc::RpcContextConfig;
|
||||||
use crate::context::{DiagnosticContext, InstallContext, SetupContext};
|
use crate::context::{DiagnosticContext, InstallContext, SetupContext};
|
||||||
use crate::disk::fsck::RepairStrategy;
|
use crate::disk::fsck::{RepairStrategy, RequiresReboot};
|
||||||
use crate::disk::main::DEFAULT_PASSWORD;
|
use crate::disk::main::DEFAULT_PASSWORD;
|
||||||
use crate::disk::REPAIR_DISK_PATH;
|
use crate::disk::REPAIR_DISK_PATH;
|
||||||
use crate::firmware::update_firmware;
|
use crate::firmware::update_firmware;
|
||||||
use crate::init::STANDBY_MODE_PATH;
|
use crate::init::STANDBY_MODE_PATH;
|
||||||
use crate::net::web_server::WebServer;
|
use crate::net::web_server::WebServer;
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
use crate::sound::CHIME;
|
use crate::sound::{BEP, CHIME};
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::{Error, ErrorKind, ResultExt, PLATFORM};
|
use crate::{Error, ErrorKind, ResultExt, PLATFORM};
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> {
|
async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Error> {
|
||||||
if update_firmware().await?.0 {
|
let song = NonDetachingJoinHandle::from(tokio::spawn(async {
|
||||||
|
loop {
|
||||||
|
BEP.play().await.unwrap();
|
||||||
|
BEP.play().await.unwrap();
|
||||||
|
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
match update_firmware().await {
|
||||||
|
Ok(RequiresReboot(true)) => {
|
||||||
return Ok(Some(Shutdown {
|
return Ok(Some(Shutdown {
|
||||||
export_args: None,
|
export_args: None,
|
||||||
restart: true,
|
restart: true,
|
||||||
}));
|
}))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Error performing firmware update: {e}");
|
||||||
|
tracing::debug!("{e:?}");
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::new("ln")
|
Command::new("ln")
|
||||||
.arg("-sf")
|
.arg("-sf")
|
||||||
.arg("/usr/lib/startos/scripts/fake-apt")
|
.arg("/usr/lib/startos/scripts/fake-apt")
|
||||||
.arg("/usr/local/bin/apt")
|
.arg("/usr/local/bin/apt")
|
||||||
.invoke(crate::ErrorKind::OpenSsh)
|
.invoke(crate::ErrorKind::Filesystem)
|
||||||
.await?;
|
.await?;
|
||||||
Command::new("ln")
|
Command::new("ln")
|
||||||
.arg("-sf")
|
.arg("-sf")
|
||||||
.arg("/usr/lib/startos/scripts/fake-apt")
|
.arg("/usr/lib/startos/scripts/fake-apt")
|
||||||
.arg("/usr/local/bin/apt-get")
|
.arg("/usr/local/bin/apt-get")
|
||||||
.invoke(crate::ErrorKind::OpenSsh)
|
.invoke(crate::ErrorKind::Filesystem)
|
||||||
.await?;
|
.await?;
|
||||||
Command::new("ln")
|
Command::new("ln")
|
||||||
.arg("-sf")
|
.arg("-sf")
|
||||||
.arg("/usr/lib/startos/scripts/fake-apt")
|
.arg("/usr/lib/startos/scripts/fake-apt")
|
||||||
.arg("/usr/local/bin/aptitude")
|
.arg("/usr/local/bin/aptitude")
|
||||||
.invoke(crate::ErrorKind::OpenSsh)
|
.invoke(crate::ErrorKind::Filesystem)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Command::new("make-ssl-cert")
|
Command::new("make-ssl-cert")
|
||||||
@@ -74,6 +90,7 @@ async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Er
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
drop(song);
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this
|
tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this
|
||||||
CHIME.play().await?;
|
CHIME.play().await?;
|
||||||
|
|
||||||
@@ -100,8 +117,10 @@ async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Er
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
drop(song);
|
||||||
tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this
|
tokio::time::sleep(Duration::from_secs(1)).await; // let the record state that I hate this
|
||||||
CHIME.play().await?;
|
CHIME.play().await?;
|
||||||
|
|
||||||
ctx.shutdown
|
ctx.shutdown
|
||||||
.subscribe()
|
.subscribe()
|
||||||
.recv()
|
.recv()
|
||||||
@@ -152,6 +171,7 @@ async fn setup_or_init(cfg_path: Option<PathBuf>) -> Result<Option<Shutdown>, Er
|
|||||||
}
|
}
|
||||||
tracing::info!("Loaded Disk");
|
tracing::info!("Loaded Disk");
|
||||||
crate::init::init(&cfg).await?;
|
crate::init::init(&cfg).await?;
|
||||||
|
drop(song);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::ops::Deref;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use helpers::to_tmp_path;
|
use helpers::to_tmp_path;
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
@@ -25,7 +26,7 @@ use crate::db::model::{CurrentDependents, Database, PackageDataEntryMatchModelRe
|
|||||||
use crate::db::prelude::PatchDbExt;
|
use crate::db::prelude::PatchDbExt;
|
||||||
use crate::dependencies::compute_dependency_config_errs;
|
use crate::dependencies::compute_dependency_config_errs;
|
||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
use crate::init::init_postgres;
|
use crate::init::{check_time_is_synchronized, init_postgres};
|
||||||
use crate::install::cleanup::{cleanup_failed, uninstall};
|
use crate::install::cleanup::{cleanup_failed, uninstall};
|
||||||
use crate::manager::ManagerMap;
|
use crate::manager::ManagerMap;
|
||||||
use crate::middleware::auth::HashSessionToken;
|
use crate::middleware::auth::HashSessionToken;
|
||||||
@@ -174,6 +175,19 @@ impl RpcContext {
|
|||||||
let tor_proxy_url = format!("socks5h://{tor_proxy}");
|
let tor_proxy_url = format!("socks5h://{tor_proxy}");
|
||||||
let devices = lshw().await?;
|
let devices = lshw().await?;
|
||||||
let ram = get_mem_info().await?.total.0 as u64 * 1024 * 1024;
|
let ram = get_mem_info().await?.total.0 as u64 * 1024 * 1024;
|
||||||
|
|
||||||
|
if !db.peek().await.as_server_info().as_ntp_synced().de()? {
|
||||||
|
let db = db.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while !check_time_is_synchronized().await.unwrap() {
|
||||||
|
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||||
|
}
|
||||||
|
db.mutate(|v| v.as_server_info_mut().as_ntp_synced_mut().ser(&true))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let seed = Arc::new(RpcContextSeed {
|
let seed = Arc::new(RpcContextSeed {
|
||||||
is_closed: AtomicBool::new(false),
|
is_closed: AtomicBool::new(false),
|
||||||
datadir: base.datadir().to_path_buf(),
|
datadir: base.datadir().to_path_buf(),
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||||
use crate::status::Status;
|
use crate::status::Status;
|
||||||
|
use crate::util::cpupower::{Governor};
|
||||||
use crate::util::Version;
|
use crate::util::Version;
|
||||||
use crate::version::{Current, VersionT};
|
use crate::version::{Current, VersionT};
|
||||||
use crate::{ARCH, PLATFORM};
|
use crate::{ARCH, PLATFORM};
|
||||||
@@ -83,6 +84,7 @@ impl Database {
|
|||||||
.join(":"),
|
.join(":"),
|
||||||
ntp_synced: false,
|
ntp_synced: false,
|
||||||
zram: true,
|
zram: true,
|
||||||
|
governor: None,
|
||||||
},
|
},
|
||||||
package_data: AllPackageData::default(),
|
package_data: AllPackageData::default(),
|
||||||
ui: serde_json::from_str(include_str!(concat!(
|
ui: serde_json::from_str(include_str!(concat!(
|
||||||
@@ -134,6 +136,7 @@ pub struct ServerInfo {
|
|||||||
pub ntp_synced: bool,
|
pub ntp_synced: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub zram: bool,
|
pub zram: bool,
|
||||||
|
pub governor: Option<Governor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::Error;
|
|||||||
pub mod btrfs;
|
pub mod btrfs;
|
||||||
pub mod ext4;
|
pub mod ext4;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct RequiresReboot(pub bool);
|
pub struct RequiresReboot(pub bool);
|
||||||
impl std::ops::BitOrAssign for RequiresReboot {
|
impl std::ops::BitOrAssign for RequiresReboot {
|
||||||
|
|||||||
@@ -1,16 +1,63 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use async_compression::tokio::bufread::GzipDecoder;
|
use async_compression::tokio::bufread::GzipDecoder;
|
||||||
|
use clap::ArgMatches;
|
||||||
|
use rpc_toolkit::command;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::{AsyncRead, BufReader};
|
use tokio::io::BufReader;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::disk::fsck::RequiresReboot;
|
use crate::disk::fsck::RequiresReboot;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
|
use crate::PLATFORM;
|
||||||
|
|
||||||
|
/// Part of the Firmware, look there for more about
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct VersionMatcher {
|
||||||
|
/// Strip this prefix on the version matcher
|
||||||
|
semver_prefix: Option<String>,
|
||||||
|
/// Match the semver to this range
|
||||||
|
semver_range: Option<semver::VersionReq>,
|
||||||
|
/// Strip this suffix on the version matcher
|
||||||
|
semver_suffix: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inside a file that is firmware.json, we
|
||||||
|
/// wanted a structure that could help decide what to do
|
||||||
|
/// for each of the firmware versions
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct Firmware {
|
||||||
|
id: String,
|
||||||
|
/// This is the platform(s) the firmware was built for
|
||||||
|
platform: BTreeSet<String>,
|
||||||
|
/// This usally comes from the dmidecode
|
||||||
|
system_product_name: Option<String>,
|
||||||
|
/// The version comes from dmidecode, then we decide if it matches
|
||||||
|
bios_version: Option<VersionMatcher>,
|
||||||
|
/// the hash of the firmware rom.gz
|
||||||
|
shasum: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_firmware_update_result(arg: RequiresReboot, _: &ArgMatches) {
|
||||||
|
if arg.0 {
|
||||||
|
println!("Firmware successfully updated! Reboot to apply changes.");
|
||||||
|
} else {
|
||||||
|
println!("No firmware update available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We wanted to make sure during every init
|
||||||
|
/// that the firmware was the correct and updated for
|
||||||
|
/// systems like the Pure System that a new firmware
|
||||||
|
/// was released and the updates where pushed through the pure os.
|
||||||
|
#[command(rename = "update-firmware", display(display_firmware_update_result))]
|
||||||
pub async fn update_firmware() -> Result<RequiresReboot, Error> {
|
pub async fn update_firmware() -> Result<RequiresReboot, Error> {
|
||||||
let product_name = String::from_utf8(
|
let system_product_name = String::from_utf8(
|
||||||
Command::new("dmidecode")
|
Command::new("dmidecode")
|
||||||
.arg("-s")
|
.arg("-s")
|
||||||
.arg("system-product-name")
|
.arg("system-product-name")
|
||||||
@@ -19,12 +66,7 @@ pub async fn update_firmware() -> Result<RequiresReboot, Error> {
|
|||||||
)?
|
)?
|
||||||
.trim()
|
.trim()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
if product_name.is_empty() {
|
let bios_version = String::from_utf8(
|
||||||
return Ok(RequiresReboot(false));
|
|
||||||
}
|
|
||||||
let firmware_dir = Path::new("/usr/lib/startos/firmware").join(&product_name);
|
|
||||||
if tokio::fs::metadata(&firmware_dir).await.is_ok() {
|
|
||||||
let current_firmware = String::from_utf8(
|
|
||||||
Command::new("dmidecode")
|
Command::new("dmidecode")
|
||||||
.arg("-s")
|
.arg("-s")
|
||||||
.arg("bios-version")
|
.arg("bios-version")
|
||||||
@@ -33,27 +75,65 @@ pub async fn update_firmware() -> Result<RequiresReboot, Error> {
|
|||||||
)?
|
)?
|
||||||
.trim()
|
.trim()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
if tokio::fs::metadata(firmware_dir.join(format!("{current_firmware}.rom.gz")))
|
if system_product_name.is_empty() || bios_version.is_empty() {
|
||||||
.await
|
return Ok(RequiresReboot(false));
|
||||||
.is_err()
|
}
|
||||||
&& tokio::fs::metadata(firmware_dir.join(format!("{current_firmware}.rom")))
|
|
||||||
.await
|
let firmware_dir = Path::new("/usr/lib/startos/firmware");
|
||||||
.is_err()
|
|
||||||
|
for firmware in serde_json::from_str::<Vec<Firmware>>(
|
||||||
|
&tokio::fs::read_to_string("/usr/lib/startos/firmware.json").await?,
|
||||||
|
)
|
||||||
|
.with_kind(ErrorKind::Deserialization)?
|
||||||
{
|
{
|
||||||
let mut firmware_read_dir = tokio::fs::read_dir(&firmware_dir).await?;
|
let id = firmware.id;
|
||||||
while let Some(entry) = firmware_read_dir.next_entry().await? {
|
let matches_product_name = firmware
|
||||||
let filename = entry.file_name().to_string_lossy().into_owned();
|
.system_product_name
|
||||||
let rdr: Option<Box<dyn AsyncRead + Unpin + Send>> =
|
.map_or(true, |spn| spn == system_product_name);
|
||||||
if filename.ends_with(".rom.gz") {
|
let matches_bios_version = firmware
|
||||||
Some(Box::new(GzipDecoder::new(BufReader::new(
|
.bios_version
|
||||||
File::open(entry.path()).await?,
|
.map_or(Some(true), |bv| {
|
||||||
|
let mut semver_str = bios_version.as_str();
|
||||||
|
if let Some(prefix) = &bv.semver_prefix {
|
||||||
|
semver_str = semver_str.strip_prefix(prefix)?;
|
||||||
|
}
|
||||||
|
if let Some(suffix) = &bv.semver_suffix {
|
||||||
|
semver_str = semver_str.strip_suffix(suffix)?;
|
||||||
|
}
|
||||||
|
let semver = semver_str
|
||||||
|
.split(".")
|
||||||
|
.filter_map(|v| v.parse().ok())
|
||||||
|
.chain(std::iter::repeat(0))
|
||||||
|
.take(3)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let semver = semver::Version::new(semver[0], semver[1], semver[2]);
|
||||||
|
Some(
|
||||||
|
bv.semver_range
|
||||||
|
.as_ref()
|
||||||
|
.map_or(true, |r| r.matches(&semver)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
if firmware.platform.contains(&*PLATFORM) && matches_product_name && matches_bios_version {
|
||||||
|
let filename = format!("{id}.rom.gz");
|
||||||
|
let firmware_path = firmware_dir.join(&filename);
|
||||||
|
Command::new("sha256sum")
|
||||||
|
.arg("-c")
|
||||||
|
.input(Some(&mut std::io::Cursor::new(format!(
|
||||||
|
"{} {}",
|
||||||
|
firmware.shasum,
|
||||||
|
firmware_path.display()
|
||||||
))))
|
))))
|
||||||
} else if filename.ends_with(".rom") {
|
.invoke(ErrorKind::Filesystem)
|
||||||
Some(Box::new(File::open(entry.path()).await?))
|
.await?;
|
||||||
|
let mut rdr = if tokio::fs::metadata(&firmware_path).await.is_ok() {
|
||||||
|
GzipDecoder::new(BufReader::new(File::open(&firmware_path).await?))
|
||||||
} else {
|
} else {
|
||||||
None
|
return Err(Error::new(
|
||||||
|
eyre!("Firmware {id}.rom.gz not found in {firmware_dir:?}"),
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
));
|
||||||
};
|
};
|
||||||
if let Some(mut rdr) = rdr {
|
|
||||||
Command::new("flashrom")
|
Command::new("flashrom")
|
||||||
.arg("-p")
|
.arg("-p")
|
||||||
.arg("internal")
|
.arg("internal")
|
||||||
@@ -64,7 +144,6 @@ pub async fn update_firmware() -> Result<RequiresReboot, Error> {
|
|||||||
return Ok(RequiresReboot(true));
|
return Ok(RequiresReboot(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(RequiresReboot(false))
|
Ok(RequiresReboot(false))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::path::Path;
|
|||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use helpers::NonDetachingJoinHandle;
|
|
||||||
use models::ResultExt;
|
use models::ResultExt;
|
||||||
use rand::random;
|
use rand::random;
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
@@ -18,9 +18,9 @@ use crate::disk::mount::util::unmount;
|
|||||||
use crate::install::PKG_ARCHIVE_DIR;
|
use crate::install::PKG_ARCHIVE_DIR;
|
||||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::sound::BEP;
|
|
||||||
use crate::util::cpupower::{
|
use crate::util::cpupower::{
|
||||||
current_governor, get_available_governors, set_governor, GOVERNOR_PERFORMANCE,
|
get_available_governors, get_preferred_governor, set_governor,
|
||||||
};
|
};
|
||||||
use crate::util::docker::{create_bridge_network, CONTAINER_DATADIR, CONTAINER_TOOL};
|
use crate::util::docker::{create_bridge_network, CONTAINER_DATADIR, CONTAINER_TOOL};
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
@@ -96,7 +96,18 @@ pub async fn init_postgres(datadir: impl AsRef<Path>) -> Result<(), Error> {
|
|||||||
|
|
||||||
let pg_version_string = pg_version.to_string();
|
let pg_version_string = pg_version.to_string();
|
||||||
let pg_version_path = db_dir.join(&pg_version_string);
|
let pg_version_path = db_dir.join(&pg_version_string);
|
||||||
if tokio::fs::metadata(&pg_version_path).await.is_err() {
|
if exists
|
||||||
|
// maybe migrate
|
||||||
|
{
|
||||||
|
let incomplete_path = db_dir.join(format!("{pg_version}.migration.incomplete"));
|
||||||
|
if tokio::fs::metadata(&incomplete_path).await.is_ok() // previous migration was incomplete
|
||||||
|
&& tokio::fs::metadata(&pg_version_path).await.is_ok()
|
||||||
|
{
|
||||||
|
tokio::fs::remove_dir_all(&pg_version_path).await?;
|
||||||
|
}
|
||||||
|
if tokio::fs::metadata(&pg_version_path).await.is_err()
|
||||||
|
// need to migrate
|
||||||
|
{
|
||||||
let conf_dir = Path::new("/etc/postgresql").join(pg_version.to_string());
|
let conf_dir = Path::new("/etc/postgresql").join(pg_version.to_string());
|
||||||
let conf_dir_tmp = {
|
let conf_dir_tmp = {
|
||||||
let mut tmp = conf_dir.clone();
|
let mut tmp = conf_dir.clone();
|
||||||
@@ -117,6 +128,10 @@ pub async fn init_postgres(datadir: impl AsRef<Path>) -> Result<(), Error> {
|
|||||||
old_version -= 1;
|
old_version -= 1;
|
||||||
let old_datadir = db_dir.join(old_version.to_string());
|
let old_datadir = db_dir.join(old_version.to_string());
|
||||||
if tokio::fs::metadata(&old_datadir).await.is_ok() {
|
if tokio::fs::metadata(&old_datadir).await.is_ok() {
|
||||||
|
tokio::fs::File::create(&incomplete_path)
|
||||||
|
.await?
|
||||||
|
.sync_all()
|
||||||
|
.await?;
|
||||||
Command::new("pg_upgradecluster")
|
Command::new("pg_upgradecluster")
|
||||||
.arg(old_version.to_string())
|
.arg(old_version.to_string())
|
||||||
.arg("main")
|
.arg("main")
|
||||||
@@ -135,6 +150,11 @@ pub async fn init_postgres(datadir: impl AsRef<Path>) -> Result<(), Error> {
|
|||||||
.invoke(ErrorKind::Filesystem)
|
.invoke(ErrorKind::Filesystem)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
tokio::fs::remove_file(&incomplete_path).await?;
|
||||||
|
}
|
||||||
|
if tokio::fs::metadata(&incomplete_path).await.is_ok() {
|
||||||
|
unreachable!() // paranoia
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::new("systemctl")
|
Command::new("systemctl")
|
||||||
@@ -230,18 +250,6 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
|||||||
|| &*server_info.version < &emver::Version::new(0, 3, 2, 0)
|
|| &*server_info.version < &emver::Version::new(0, 3, 2, 0)
|
||||||
|| (*ARCH == "x86_64" && &*server_info.version < &emver::Version::new(0, 3, 4, 0));
|
|| (*ARCH == "x86_64" && &*server_info.version < &emver::Version::new(0, 3, 4, 0));
|
||||||
|
|
||||||
let song = if should_rebuild {
|
|
||||||
Some(NonDetachingJoinHandle::from(tokio::spawn(async {
|
|
||||||
loop {
|
|
||||||
BEP.play().await.unwrap();
|
|
||||||
BEP.play().await.unwrap();
|
|
||||||
tokio::time::sleep(Duration::from_secs(60)).await;
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let log_dir = cfg.datadir().join("main/logs");
|
let log_dir = cfg.datadir().join("main/logs");
|
||||||
if tokio::fs::metadata(&log_dir).await.is_err() {
|
if tokio::fs::metadata(&log_dir).await.is_err() {
|
||||||
tokio::fs::create_dir_all(&log_dir).await?;
|
tokio::fs::create_dir_all(&log_dir).await?;
|
||||||
@@ -318,12 +326,13 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
|||||||
tracing::info!("Created Docker Network");
|
tracing::info!("Created Docker Network");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let datadir = cfg.datadir();
|
||||||
tracing::info!("Loading System Docker Images");
|
tracing::info!("Loading System Docker Images");
|
||||||
crate::install::load_images("/usr/lib/startos/system-images").await?;
|
crate::install::rebuild_from("/usr/lib/startos/system-images", &datadir).await?;
|
||||||
tracing::info!("Loaded System Docker Images");
|
tracing::info!("Loaded System Docker Images");
|
||||||
|
|
||||||
tracing::info!("Loading Package Docker Images");
|
tracing::info!("Loading Package Docker Images");
|
||||||
crate::install::load_images(cfg.datadir().join(PKG_ARCHIVE_DIR)).await?;
|
crate::install::rebuild_from(datadir.join(PKG_ARCHIVE_DIR), &datadir).await?;
|
||||||
tracing::info!("Loaded Package Docker Images");
|
tracing::info!("Loaded Package Docker Images");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +342,7 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
|||||||
.arg("run")
|
.arg("run")
|
||||||
.arg("-d")
|
.arg("-d")
|
||||||
.arg("--rm")
|
.arg("--rm")
|
||||||
|
.arg("--init")
|
||||||
.arg("--network=start9")
|
.arg("--network=start9")
|
||||||
.arg("--name=netdummy")
|
.arg("--name=netdummy")
|
||||||
.arg("start9/x_system/utils:latest")
|
.arg("start9/x_system/utils:latest")
|
||||||
@@ -354,28 +364,27 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
|||||||
.await?;
|
.await?;
|
||||||
tracing::info!("Enabled Docker QEMU Emulation");
|
tracing::info!("Enabled Docker QEMU Emulation");
|
||||||
|
|
||||||
if current_governor()
|
let governor = if let Some(governor) = &server_info.governor {
|
||||||
.await?
|
if get_available_governors().await?.contains(governor) {
|
||||||
.map(|g| &g != &GOVERNOR_PERFORMANCE)
|
Some(governor)
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
tracing::info!("Setting CPU Governor to \"{}\"", GOVERNOR_PERFORMANCE);
|
|
||||||
if get_available_governors()
|
|
||||||
.await?
|
|
||||||
.contains(&GOVERNOR_PERFORMANCE)
|
|
||||||
{
|
|
||||||
set_governor(&GOVERNOR_PERFORMANCE).await?;
|
|
||||||
tracing::info!("Set CPU Governor");
|
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("CPU Governor \"{}\" Not Available", GOVERNOR_PERFORMANCE)
|
tracing::warn!("CPU Governor \"{governor}\" Not Available");
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
get_preferred_governor().await?
|
||||||
|
};
|
||||||
|
if let Some(governor) = governor {
|
||||||
|
tracing::info!("Setting CPU Governor to \"{governor}\"");
|
||||||
|
set_governor(governor).await?;
|
||||||
|
tracing::info!("Set CPU Governor");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut time_not_synced = true;
|
server_info.ntp_synced = false;
|
||||||
let mut not_made_progress = 0u32;
|
let mut not_made_progress = 0u32;
|
||||||
for _ in 0..1800 {
|
for _ in 0..1800 {
|
||||||
if check_time_is_synchronized().await? {
|
if check_time_is_synchronized().await? {
|
||||||
time_not_synced = false;
|
server_info.ntp_synced = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let t = SystemTime::now();
|
let t = SystemTime::now();
|
||||||
@@ -392,7 +401,7 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if time_not_synced {
|
if !server_info.ntp_synced {
|
||||||
tracing::warn!("Timed out waiting for system time to synchronize");
|
tracing::warn!("Timed out waiting for system time to synchronize");
|
||||||
} else {
|
} else {
|
||||||
tracing::info!("Syncronized system clock");
|
tracing::info!("Syncronized system clock");
|
||||||
@@ -410,21 +419,6 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
|||||||
restarting: false,
|
restarting: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
server_info.ntp_synced = if time_not_synced {
|
|
||||||
let db = db.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
while !check_time_is_synchronized().await.unwrap() {
|
|
||||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
|
||||||
}
|
|
||||||
db.mutate(|v| v.as_server_info_mut().as_ntp_synced_mut().ser(&true))
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
};
|
|
||||||
|
|
||||||
db.mutate(|v| {
|
db.mutate(|v| {
|
||||||
v.as_server_info_mut().ser(&server_info)?;
|
v.as_server_info_mut().ser(&server_info)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -447,8 +441,6 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
|||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(song);
|
|
||||||
|
|
||||||
tracing::info!("System initialized.");
|
tracing::info!("System initialized.");
|
||||||
|
|
||||||
Ok(InitResult { secret_store, db })
|
Ok(InitResult { secret_store, db })
|
||||||
|
|||||||
@@ -891,102 +891,11 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
|||||||
}
|
}
|
||||||
tracing::info!("Install {}@{}: Fetched Dependency Info", pkg_id, version);
|
tracing::info!("Install {}@{}: Fetched Dependency Info", pkg_id, version);
|
||||||
|
|
||||||
let public_dir_path = ctx
|
let icon = progress
|
||||||
.datadir
|
.track_read_during(ctx.db.clone(), pkg_id, || {
|
||||||
.join(PKG_PUBLIC_DIR)
|
unpack_s9pk(&ctx.datadir, &manifest, rdr)
|
||||||
.join(pkg_id)
|
|
||||||
.join(version.as_str());
|
|
||||||
tokio::fs::create_dir_all(&public_dir_path).await?;
|
|
||||||
|
|
||||||
tracing::info!("Install {}@{}: Unpacking LICENSE.md", pkg_id, version);
|
|
||||||
progress
|
|
||||||
.track_read_during(ctx.db.clone(), pkg_id, || async {
|
|
||||||
let license_path = public_dir_path.join("LICENSE.md");
|
|
||||||
let mut dst = File::create(&license_path).await?;
|
|
||||||
tokio::io::copy(&mut rdr.license().await?, &mut dst).await?;
|
|
||||||
dst.sync_all().await?;
|
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
tracing::info!("Install {}@{}: Unpacked LICENSE.md", pkg_id, version);
|
|
||||||
|
|
||||||
tracing::info!("Install {}@{}: Unpacking INSTRUCTIONS.md", pkg_id, version);
|
|
||||||
progress
|
|
||||||
.track_read_during(ctx.db.clone(), pkg_id, || async {
|
|
||||||
let instructions_path = public_dir_path.join("INSTRUCTIONS.md");
|
|
||||||
let mut dst = File::create(&instructions_path).await?;
|
|
||||||
tokio::io::copy(&mut rdr.instructions().await?, &mut dst).await?;
|
|
||||||
dst.sync_all().await?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
tracing::info!("Install {}@{}: Unpacked INSTRUCTIONS.md", pkg_id, version);
|
|
||||||
|
|
||||||
let icon_filename = Path::new("icon").with_extension(manifest.assets.icon_type());
|
|
||||||
let icon_path = public_dir_path.join(&icon_filename);
|
|
||||||
tracing::info!(
|
|
||||||
"Install {}@{}: Unpacking {}",
|
|
||||||
pkg_id,
|
|
||||||
version,
|
|
||||||
icon_path.display()
|
|
||||||
);
|
|
||||||
let icon_buf = progress
|
|
||||||
.track_read_during(ctx.db.clone(), pkg_id, || async {
|
|
||||||
Ok(rdr.icon().await?.to_vec().await?)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
let mut dst = File::create(&icon_path).await?;
|
|
||||||
dst.write_all(&icon_buf).await?;
|
|
||||||
dst.sync_all().await?;
|
|
||||||
let icon = DataUrl::from_vec(
|
|
||||||
mime(manifest.assets.icon_type()).unwrap_or("image/png"),
|
|
||||||
icon_buf,
|
|
||||||
);
|
|
||||||
tracing::info!(
|
|
||||||
"Install {}@{}: Unpacked {}",
|
|
||||||
pkg_id,
|
|
||||||
version,
|
|
||||||
icon_filename.display()
|
|
||||||
);
|
|
||||||
|
|
||||||
tracing::info!("Install {}@{}: Unpacking Docker Images", pkg_id, version);
|
|
||||||
progress
|
|
||||||
.track_read_during(ctx.db.clone(), pkg_id, || async {
|
|
||||||
Command::new(CONTAINER_TOOL)
|
|
||||||
.arg("load")
|
|
||||||
.input(Some(&mut rdr.docker_images().await?))
|
|
||||||
.invoke(ErrorKind::Docker)
|
|
||||||
.await
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
tracing::info!("Install {}@{}: Unpacked Docker Images", pkg_id, version,);
|
|
||||||
|
|
||||||
tracing::info!("Install {}@{}: Unpacking Assets", pkg_id, version);
|
|
||||||
progress
|
|
||||||
.track_read_during(ctx.db.clone(), pkg_id, || async {
|
|
||||||
let asset_dir = asset_dir(&ctx.datadir, pkg_id, version);
|
|
||||||
if tokio::fs::metadata(&asset_dir).await.is_err() {
|
|
||||||
tokio::fs::create_dir_all(&asset_dir).await?;
|
|
||||||
}
|
|
||||||
let mut tar = tokio_tar::Archive::new(rdr.assets().await?);
|
|
||||||
tar.unpack(asset_dir).await?;
|
|
||||||
|
|
||||||
let script_dir = script_dir(&ctx.datadir, pkg_id, version);
|
|
||||||
if tokio::fs::metadata(&script_dir).await.is_err() {
|
|
||||||
tokio::fs::create_dir_all(&script_dir).await?;
|
|
||||||
}
|
|
||||||
if let Some(mut hdl) = rdr.scripts().await? {
|
|
||||||
tokio::io::copy(
|
|
||||||
&mut hdl,
|
|
||||||
&mut File::create(script_dir.join("embassy.js")).await?,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
tracing::info!("Install {}@{}: Unpacked Assets", pkg_id, version);
|
|
||||||
|
|
||||||
progress.unpack_complete.store(true, Ordering::SeqCst);
|
progress.unpack_complete.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
@@ -1107,6 +1016,8 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
|||||||
let mut auto_start = false;
|
let mut auto_start = false;
|
||||||
let mut configured = false;
|
let mut configured = false;
|
||||||
|
|
||||||
|
let mut to_cleanup = None;
|
||||||
|
|
||||||
if let PackageDataEntry::Updating(PackageDataEntryUpdating {
|
if let PackageDataEntry::Updating(PackageDataEntryUpdating {
|
||||||
installed: prev, ..
|
installed: prev, ..
|
||||||
}) = &prev
|
}) = &prev
|
||||||
@@ -1148,7 +1059,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
|||||||
auto_start = prev.status.main.running();
|
auto_start = prev.status.main.running();
|
||||||
}
|
}
|
||||||
if &prev.manifest.version != version {
|
if &prev.manifest.version != version {
|
||||||
cleanup(&ctx, &prev.manifest.id, &prev.manifest.version).await?;
|
to_cleanup = Some((prev.manifest.id.clone(), prev.manifest.version.clone()));
|
||||||
}
|
}
|
||||||
} else if let PackageDataEntry::Restoring(PackageDataEntryRestoring { .. }) = prev {
|
} else if let PackageDataEntry::Restoring(PackageDataEntryRestoring { .. }) = prev {
|
||||||
next.installed.marketplace_url = manifest
|
next.installed.marketplace_url = manifest
|
||||||
@@ -1191,6 +1102,10 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
|||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
if let Some((id, version)) = to_cleanup {
|
||||||
|
cleanup(&ctx, &id, &version).await?;
|
||||||
|
}
|
||||||
|
|
||||||
if configured && manifest.config.is_some() {
|
if configured && manifest.config.is_some() {
|
||||||
let breakages = BTreeMap::new();
|
let breakages = BTreeMap::new();
|
||||||
let overrides = Default::default();
|
let overrides = Default::default();
|
||||||
@@ -1237,15 +1152,103 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub fn load_images<'a, P: AsRef<Path> + 'a + Send + Sync>(
|
pub async fn unpack_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
||||||
datadir: P,
|
datadir: impl AsRef<Path>,
|
||||||
|
manifest: &Manifest,
|
||||||
|
rdr: &mut S9pkReader<R>,
|
||||||
|
) -> Result<DataUrl<'static>, Error> {
|
||||||
|
let datadir = datadir.as_ref();
|
||||||
|
let pkg_id = &manifest.id;
|
||||||
|
let version = &manifest.version;
|
||||||
|
|
||||||
|
let public_dir_path = datadir
|
||||||
|
.join(PKG_PUBLIC_DIR)
|
||||||
|
.join(pkg_id)
|
||||||
|
.join(version.as_str());
|
||||||
|
tokio::fs::create_dir_all(&public_dir_path).await?;
|
||||||
|
|
||||||
|
tracing::info!("Install {}@{}: Unpacking LICENSE.md", pkg_id, version);
|
||||||
|
let license_path = public_dir_path.join("LICENSE.md");
|
||||||
|
let mut dst = File::create(&license_path).await?;
|
||||||
|
tokio::io::copy(&mut rdr.license().await?, &mut dst).await?;
|
||||||
|
dst.sync_all().await?;
|
||||||
|
tracing::info!("Install {}@{}: Unpacked LICENSE.md", pkg_id, version);
|
||||||
|
|
||||||
|
tracing::info!("Install {}@{}: Unpacking INSTRUCTIONS.md", pkg_id, version);
|
||||||
|
let instructions_path = public_dir_path.join("INSTRUCTIONS.md");
|
||||||
|
let mut dst = File::create(&instructions_path).await?;
|
||||||
|
tokio::io::copy(&mut rdr.instructions().await?, &mut dst).await?;
|
||||||
|
dst.sync_all().await?;
|
||||||
|
tracing::info!("Install {}@{}: Unpacked INSTRUCTIONS.md", pkg_id, version);
|
||||||
|
|
||||||
|
let icon_filename = Path::new("icon").with_extension(manifest.assets.icon_type());
|
||||||
|
let icon_path = public_dir_path.join(&icon_filename);
|
||||||
|
tracing::info!(
|
||||||
|
"Install {}@{}: Unpacking {}",
|
||||||
|
pkg_id,
|
||||||
|
version,
|
||||||
|
icon_path.display()
|
||||||
|
);
|
||||||
|
let icon_buf = rdr.icon().await?.to_vec().await?;
|
||||||
|
let mut dst = File::create(&icon_path).await?;
|
||||||
|
dst.write_all(&icon_buf).await?;
|
||||||
|
dst.sync_all().await?;
|
||||||
|
let icon = DataUrl::from_vec(
|
||||||
|
mime(manifest.assets.icon_type()).unwrap_or("image/png"),
|
||||||
|
icon_buf,
|
||||||
|
);
|
||||||
|
tracing::info!(
|
||||||
|
"Install {}@{}: Unpacked {}",
|
||||||
|
pkg_id,
|
||||||
|
version,
|
||||||
|
icon_filename.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
tracing::info!("Install {}@{}: Unpacking Docker Images", pkg_id, version);
|
||||||
|
Command::new(CONTAINER_TOOL)
|
||||||
|
.arg("load")
|
||||||
|
.input(Some(&mut rdr.docker_images().await?))
|
||||||
|
.invoke(ErrorKind::Docker)
|
||||||
|
.await?;
|
||||||
|
tracing::info!("Install {}@{}: Unpacked Docker Images", pkg_id, version,);
|
||||||
|
|
||||||
|
tracing::info!("Install {}@{}: Unpacking Assets", pkg_id, version);
|
||||||
|
let asset_dir = asset_dir(datadir, pkg_id, version);
|
||||||
|
if tokio::fs::metadata(&asset_dir).await.is_ok() {
|
||||||
|
tokio::fs::remove_dir_all(&asset_dir).await?;
|
||||||
|
}
|
||||||
|
tokio::fs::create_dir_all(&asset_dir).await?;
|
||||||
|
let mut tar = tokio_tar::Archive::new(rdr.assets().await?);
|
||||||
|
tar.unpack(asset_dir).await?;
|
||||||
|
|
||||||
|
let script_dir = script_dir(datadir, pkg_id, version);
|
||||||
|
if tokio::fs::metadata(&script_dir).await.is_err() {
|
||||||
|
tokio::fs::create_dir_all(&script_dir).await?;
|
||||||
|
}
|
||||||
|
if let Some(mut hdl) = rdr.scripts().await? {
|
||||||
|
tokio::io::copy(
|
||||||
|
&mut hdl,
|
||||||
|
&mut File::create(script_dir.join("embassy.js")).await?,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
tracing::info!("Install {}@{}: Unpacked Assets", pkg_id, version);
|
||||||
|
|
||||||
|
Ok(icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub fn rebuild_from<'a>(
|
||||||
|
source: impl AsRef<Path> + 'a + Send + Sync,
|
||||||
|
datadir: impl AsRef<Path> + 'a + Send + Sync,
|
||||||
) -> BoxFuture<'a, Result<(), Error>> {
|
) -> BoxFuture<'a, Result<(), Error>> {
|
||||||
async move {
|
async move {
|
||||||
let docker_dir = datadir.as_ref();
|
let source_dir = source.as_ref();
|
||||||
if tokio::fs::metadata(&docker_dir).await.is_ok() {
|
let datadir = datadir.as_ref();
|
||||||
ReadDirStream::new(tokio::fs::read_dir(&docker_dir).await?)
|
if tokio::fs::metadata(&source_dir).await.is_ok() {
|
||||||
|
ReadDirStream::new(tokio::fs::read_dir(&source_dir).await?)
|
||||||
.map(|r| {
|
.map(|r| {
|
||||||
r.with_ctx(|_| (crate::ErrorKind::Filesystem, format!("{:?}", &docker_dir)))
|
r.with_ctx(|_| (crate::ErrorKind::Filesystem, format!("{:?}", &source_dir)))
|
||||||
})
|
})
|
||||||
.try_for_each(|entry| async move {
|
.try_for_each(|entry| async move {
|
||||||
let m = entry.metadata().await?;
|
let m = entry.metadata().await?;
|
||||||
@@ -1260,26 +1263,21 @@ pub fn load_images<'a, P: AsRef<Path> + 'a + Send + Sync>(
|
|||||||
.arg("load")
|
.arg("load")
|
||||||
.input(Some(&mut File::open(&path).await?))
|
.input(Some(&mut File::open(&path).await?))
|
||||||
.invoke(ErrorKind::Docker)
|
.invoke(ErrorKind::Docker)
|
||||||
.await
|
.await?;
|
||||||
|
Ok::<_, Error>(())
|
||||||
}
|
}
|
||||||
Some("s9pk") => {
|
Some("s9pk") => {
|
||||||
Command::new(CONTAINER_TOOL)
|
let mut s9pk = S9pkReader::open(&path, true).await?;
|
||||||
.arg("load")
|
unpack_s9pk(datadir, &s9pk.manifest().await?, &mut s9pk)
|
||||||
.input(Some(
|
.await?;
|
||||||
&mut S9pkReader::open(&path, true)
|
Ok(())
|
||||||
.await?
|
|
||||||
.docker_images()
|
|
||||||
.await?,
|
|
||||||
))
|
|
||||||
.invoke(ErrorKind::Docker)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
tracing::error!("Error loading docker images from s9pk: {e}");
|
tracing::error!("Error unpacking {path:?}: {e}");
|
||||||
tracing::debug!("{e:?}");
|
tracing::debug!("{e:?}");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1287,7 +1285,7 @@ pub fn load_images<'a, P: AsRef<Path> + 'a + Send + Sync>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
} else if m.is_dir() {
|
} else if m.is_dir() {
|
||||||
load_images(entry.path()).await?;
|
rebuild_from(entry.path(), datadir).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#![recursion_limit = "256"]
|
|
||||||
|
|
||||||
pub const DEFAULT_MARKETPLACE: &str = "https://registry.start9.com";
|
pub const DEFAULT_MARKETPLACE: &str = "https://registry.start9.com";
|
||||||
// pub const COMMUNITY_MARKETPLACE: &str = "https://community-registry.start9.com";
|
// pub const COMMUNITY_MARKETPLACE: &str = "https://community-registry.start9.com";
|
||||||
pub const BUFFER_SIZE: usize = 1024;
|
pub const BUFFER_SIZE: usize = 1024;
|
||||||
@@ -107,6 +105,7 @@ pub fn main_api() -> Result<(), RpcError> {
|
|||||||
shutdown::restart,
|
shutdown::restart,
|
||||||
shutdown::rebuild,
|
shutdown::rebuild,
|
||||||
update::update_system,
|
update::update_system,
|
||||||
|
firmware::update_firmware,
|
||||||
))]
|
))]
|
||||||
pub fn server() -> Result<(), RpcError> {
|
pub fn server() -> Result<(), RpcError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use clap::ArgMatches;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use models::{Id, InterfaceId, PackageId};
|
use models::{Id, InterfaceId, PackageId};
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
use openssl::sha::Sha256;
|
use openssl::sha::Sha256;
|
||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
use p256::elliptic_curve::pkcs8::EncodePrivateKey;
|
use p256::elliptic_curve::pkcs8::EncodePrivateKey;
|
||||||
use sqlx::PgExecutor;
|
use rpc_toolkit::command;
|
||||||
|
use sqlx::{Acquire, PgExecutor};
|
||||||
use ssh_key::private::Ed25519PrivateKey;
|
use ssh_key::private::Ed25519PrivateKey;
|
||||||
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crate::config::{configure, ConfigureContext};
|
||||||
|
use crate::context::RpcContext;
|
||||||
|
use crate::control::restart;
|
||||||
|
use crate::disk::fsck::RequiresReboot;
|
||||||
use crate::net::ssl::CertPair;
|
use crate::net::ssl::CertPair;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::crypto::ed25519_expand_key;
|
use crate::util::crypto::ed25519_expand_key;
|
||||||
@@ -271,3 +279,107 @@ pub fn test_keygen() {
|
|||||||
key.tor_key();
|
key.tor_key();
|
||||||
key.openssl_key_nistp256();
|
key.openssl_key_nistp256();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn display_requires_reboot(arg: RequiresReboot, _matches: &ArgMatches) {
|
||||||
|
if arg.0 {
|
||||||
|
println!("Server must be restarted for changes to take effect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(rename = "rotate-key", display(display_requires_reboot))]
|
||||||
|
pub async fn rotate_key(
|
||||||
|
#[context] ctx: RpcContext,
|
||||||
|
#[arg] package: Option<PackageId>,
|
||||||
|
#[arg] interface: Option<InterfaceId>,
|
||||||
|
) -> Result<RequiresReboot, Error> {
|
||||||
|
let mut pgcon = ctx.secret_store.acquire().await?;
|
||||||
|
let mut tx = pgcon.begin().await?;
|
||||||
|
if let Some(package) = package {
|
||||||
|
let Some(interface) = interface else {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("Must specify interface"),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
sqlx::query!(
|
||||||
|
"DELETE FROM tor WHERE package = $1 AND interface = $2",
|
||||||
|
&package,
|
||||||
|
&interface,
|
||||||
|
)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await?;
|
||||||
|
sqlx::query!(
|
||||||
|
"DELETE FROM network_keys WHERE package = $1 AND interface = $2",
|
||||||
|
&package,
|
||||||
|
&interface,
|
||||||
|
)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await?;
|
||||||
|
let new_key =
|
||||||
|
Key::for_interface(&mut *tx, Some((package.clone(), interface.clone()))).await?;
|
||||||
|
let needs_config = ctx
|
||||||
|
.db
|
||||||
|
.mutate(|v| {
|
||||||
|
let installed = v
|
||||||
|
.as_package_data_mut()
|
||||||
|
.as_idx_mut(&package)
|
||||||
|
.or_not_found(&package)?
|
||||||
|
.as_installed_mut()
|
||||||
|
.or_not_found("installed")?;
|
||||||
|
let addrs = installed
|
||||||
|
.as_interface_addresses_mut()
|
||||||
|
.as_idx_mut(&interface)
|
||||||
|
.or_not_found(&interface)?;
|
||||||
|
if let Some(lan) = addrs.as_lan_address_mut().transpose_mut() {
|
||||||
|
lan.ser(&new_key.local_address())?;
|
||||||
|
}
|
||||||
|
if let Some(lan) = addrs.as_tor_address_mut().transpose_mut() {
|
||||||
|
lan.ser(&new_key.tor_address().to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if installed
|
||||||
|
.as_manifest()
|
||||||
|
.as_config()
|
||||||
|
.transpose_ref()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
installed
|
||||||
|
.as_status_mut()
|
||||||
|
.as_configured_mut()
|
||||||
|
.replace(&false)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
if needs_config {
|
||||||
|
configure(
|
||||||
|
&ctx,
|
||||||
|
&package,
|
||||||
|
ConfigureContext {
|
||||||
|
breakages: BTreeMap::new(),
|
||||||
|
timeout: None,
|
||||||
|
config: None,
|
||||||
|
overrides: BTreeMap::new(),
|
||||||
|
dry_run: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
restart(ctx, package).await?;
|
||||||
|
}
|
||||||
|
Ok(RequiresReboot(false))
|
||||||
|
} else {
|
||||||
|
sqlx::query!("UPDATE account SET tor_key = NULL, network_key = gen_random_bytes(32)")
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await?;
|
||||||
|
let new_key = Key::for_interface(&mut *tx, None).await?;
|
||||||
|
let url = format!("https://{}", new_key.tor_address()).parse()?;
|
||||||
|
ctx.db
|
||||||
|
.mutate(|v| v.as_server_info_mut().as_tor_address_mut().ser(&url))
|
||||||
|
.await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
Ok(RequiresReboot(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub mod wifi;
|
|||||||
|
|
||||||
pub const PACKAGE_CERT_PATH: &str = "/var/lib/embassy/ssl";
|
pub const PACKAGE_CERT_PATH: &str = "/var/lib/embassy/ssl";
|
||||||
|
|
||||||
#[command(subcommands(tor::tor, dhcp::dhcp, ssl::ssl))]
|
#[command(subcommands(tor::tor, dhcp::dhcp, ssl::ssl, keys::rotate_key))]
|
||||||
pub fn net() -> Result<(), Error> {
|
pub fn net() -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ use tokio_util::io::ReaderStream;
|
|||||||
use crate::context::{DiagnosticContext, InstallContext, RpcContext, SetupContext};
|
use crate::context::{DiagnosticContext, InstallContext, RpcContext, SetupContext};
|
||||||
use crate::core::rpc_continuations::RequestGuid;
|
use crate::core::rpc_continuations::RequestGuid;
|
||||||
use crate::db::subscribe;
|
use crate::db::subscribe;
|
||||||
|
use crate::hostname::Hostname;
|
||||||
use crate::install::PKG_PUBLIC_DIR;
|
use crate::install::PKG_PUBLIC_DIR;
|
||||||
use crate::middleware::auth::{auth as auth_middleware, HasValidSession};
|
use crate::middleware::auth::{auth as auth_middleware, HasValidSession};
|
||||||
use crate::middleware::cors::cors;
|
use crate::middleware::cors::cors;
|
||||||
@@ -339,7 +340,8 @@ async fn main_embassy_ui(req: Request<Body>, ctx: RpcContext) -> Result<Response
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
(&Method::GET, Some(("eos", "local.crt"))) => {
|
(&Method::GET, Some(("eos", "local.crt"))) => {
|
||||||
cert_send(&ctx.account.read().await.root_ca_cert)
|
let account = ctx.account.read().await;
|
||||||
|
cert_send(&account.root_ca_cert, &account.hostname)
|
||||||
}
|
}
|
||||||
(&Method::GET, _) => {
|
(&Method::GET, _) => {
|
||||||
let uri_path = UiMode::Main.path(
|
let uri_path = UiMode::Main.path(
|
||||||
@@ -405,7 +407,7 @@ fn bad_request() -> Response<Body> {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cert_send(cert: &X509) -> Result<Response<Body>, Error> {
|
fn cert_send(cert: &X509, hostname: &Hostname) -> Result<Response<Body>, Error> {
|
||||||
let pem = cert.to_pem()?;
|
let pem = cert.to_pem()?;
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(StatusCode::OK)
|
.status(StatusCode::OK)
|
||||||
@@ -417,8 +419,12 @@ fn cert_send(cert: &X509) -> Result<Response<Body>, Error> {
|
|||||||
)
|
)
|
||||||
.to_lowercase(),
|
.to_lowercase(),
|
||||||
)
|
)
|
||||||
.header(http::header::CONTENT_TYPE, "application/x-pem-file")
|
.header(http::header::CONTENT_TYPE, "application/x-x509-ca-cert")
|
||||||
.header(http::header::CONTENT_LENGTH, pem.len())
|
.header(http::header::CONTENT_LENGTH, pem.len())
|
||||||
|
.header(
|
||||||
|
http::header::CONTENT_DISPOSITION,
|
||||||
|
format!("attachment; filename={}.crt", &hostname.0),
|
||||||
|
)
|
||||||
.body(Body::from(pem))
|
.body(Body::from(pem))
|
||||||
.with_kind(ErrorKind::Network)
|
.with_kind(ErrorKind::Network)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,11 +53,6 @@ lazy_static! {
|
|||||||
static ref PROGRESS_REGEX: Regex = Regex::new("PROGRESS=([0-9]+)").unwrap();
|
static ref PROGRESS_REGEX: Regex = Regex::new("PROGRESS=([0-9]+)").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn random_key() {
|
|
||||||
println!("x'{}'", hex::encode(rand::random::<[u8; 32]>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(subcommands(list_services, logs, reset))]
|
#[command(subcommands(list_services, logs, reset))]
|
||||||
pub fn tor() -> Result<(), Error> {
|
pub fn tor() -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -684,6 +679,7 @@ impl TorControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[ignore]
|
||||||
async fn test() {
|
async fn test() {
|
||||||
let mut conn = torut::control::UnauthenticatedConn::new(
|
let mut conn = torut::control::UnauthenticatedConn::new(
|
||||||
TcpStream::connect(SocketAddr::from(([127, 0, 0, 1], 9051)))
|
TcpStream::connect(SocketAddr::from(([127, 0, 0, 1], 9051)))
|
||||||
|
|||||||
@@ -81,8 +81,12 @@ impl JsProcedure {
|
|||||||
_gid: ProcessGroupId,
|
_gid: ProcessGroupId,
|
||||||
_rpc_client: Option<Arc<UnixRpcClient>>,
|
_rpc_client: Option<Arc<UnixRpcClient>>,
|
||||||
) -> Result<Result<O, (i32, String)>, Error> {
|
) -> Result<Result<O, (i32, String)>, Error> {
|
||||||
Command::new("start-deno")
|
#[cfg(not(test))]
|
||||||
.arg("execute")
|
let mut cmd = Command::new("start-deno");
|
||||||
|
#[cfg(test)]
|
||||||
|
let mut cmd = test_start_deno_command().await?;
|
||||||
|
|
||||||
|
cmd.arg("execute")
|
||||||
.input(Some(&mut std::io::Cursor::new(IoFormat::Json.to_vec(
|
.input(Some(&mut std::io::Cursor::new(IoFormat::Json.to_vec(
|
||||||
&ExecuteArgs {
|
&ExecuteArgs {
|
||||||
procedure: self.clone(),
|
procedure: self.clone(),
|
||||||
@@ -111,8 +115,12 @@ impl JsProcedure {
|
|||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
name: ProcedureName,
|
name: ProcedureName,
|
||||||
) -> Result<Result<O, (i32, String)>, Error> {
|
) -> Result<Result<O, (i32, String)>, Error> {
|
||||||
Command::new("start-deno")
|
#[cfg(not(test))]
|
||||||
.arg("sandbox")
|
let mut cmd = Command::new("start-deno");
|
||||||
|
#[cfg(test)]
|
||||||
|
let mut cmd = test_start_deno_command().await?;
|
||||||
|
|
||||||
|
cmd.arg("sandbox")
|
||||||
.input(Some(&mut std::io::Cursor::new(IoFormat::Json.to_vec(
|
.input(Some(&mut std::io::Cursor::new(IoFormat::Json.to_vec(
|
||||||
&ExecuteArgs {
|
&ExecuteArgs {
|
||||||
procedure: self.clone(),
|
procedure: self.clone(),
|
||||||
@@ -212,6 +220,26 @@ fn unwrap_known_error<O: DeserializeOwned>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
async fn test_start_deno_command() -> Result<Command, Error> {
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg("build")
|
||||||
|
.invoke(ErrorKind::Unknown)
|
||||||
|
.await?;
|
||||||
|
if tokio::fs::metadata("../target/debug/start-deno")
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
Command::new("ln")
|
||||||
|
.arg("-rsf")
|
||||||
|
.arg("../target/debug/startbox")
|
||||||
|
.arg("../target/debug/start-deno")
|
||||||
|
.invoke(crate::ErrorKind::Filesystem)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(Command::new("../target/debug/start-deno"))
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn js_action_execute() {
|
async fn js_action_execute() {
|
||||||
let js_action = JsProcedure { args: vec![] };
|
let js_action = JsProcedure { args: vec![] };
|
||||||
|
|||||||
@@ -179,5 +179,7 @@ impl<'de> Deserialize<'de> for NoOutput {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_deser_no_output() {
|
fn test_deser_no_output() {
|
||||||
serde_json::from_str::<NoOutput>("").unwrap();
|
serde_json::from_str::<NoOutput>("").unwrap();
|
||||||
serde_json::from_str::<Result<NoOutput, NoOutput>>("{\"Ok\": null}").unwrap();
|
serde_json::from_str::<Result<NoOutput, NoOutput>>("{\"Ok\": null}")
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,17 @@ impl Shutdown {
|
|||||||
tracing::error!("Error Stopping Docker: {}", e);
|
tracing::error!("Error Stopping Docker: {}", e);
|
||||||
tracing::debug!("{:?}", e);
|
tracing::debug!("{:?}", e);
|
||||||
}
|
}
|
||||||
|
} else if CONTAINER_TOOL == "podman" {
|
||||||
|
if let Err(e) = Command::new("podman")
|
||||||
|
.arg("rm")
|
||||||
|
.arg("-f")
|
||||||
|
.arg("netdummy")
|
||||||
|
.invoke(crate::ErrorKind::Docker)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("Error Stopping Podman: {}", e);
|
||||||
|
tracing::debug!("{:?}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some((guid, datadir)) = &self.export_args {
|
if let Some((guid, datadir)) = &self.export_args {
|
||||||
if let Err(e) = export(guid, datadir).await {
|
if let Err(e) = export(guid, datadir).await {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@@ -20,11 +21,12 @@ use crate::logs::{
|
|||||||
};
|
};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
|
use crate::util::cpupower::{get_available_governors, set_governor, Governor};
|
||||||
use crate::util::serde::{display_serializable, IoFormat};
|
use crate::util::serde::{display_serializable, IoFormat};
|
||||||
use crate::util::{display_none, Invoke};
|
use crate::util::{display_none, Invoke};
|
||||||
use crate::{Error, ErrorKind, ResultExt};
|
use crate::{Error, ErrorKind, ResultExt};
|
||||||
|
|
||||||
#[command(subcommands(zram))]
|
#[command(subcommands(zram, governor))]
|
||||||
pub async fn experimental() -> Result<(), Error> {
|
pub async fn experimental() -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -85,6 +87,56 @@ pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(),
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct GovernorInfo {
|
||||||
|
current: Option<Governor>,
|
||||||
|
available: BTreeSet<Governor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_governor_info(arg: GovernorInfo, matches: &ArgMatches) {
|
||||||
|
use prettytable::*;
|
||||||
|
|
||||||
|
if matches.is_present("format") {
|
||||||
|
return display_serializable(arg, matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut table = Table::new();
|
||||||
|
table.add_row(row![bc -> "GOVERNORS"]);
|
||||||
|
for entry in arg.available {
|
||||||
|
if Some(&entry) == arg.current.as_ref() {
|
||||||
|
table.add_row(row![g -> format!("* {entry} (current)")]);
|
||||||
|
} else {
|
||||||
|
table.add_row(row![entry]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.print_tty(false).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(display(display_governor_info))]
|
||||||
|
pub async fn governor(
|
||||||
|
#[context] ctx: RpcContext,
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[arg(long = "format")]
|
||||||
|
format: Option<IoFormat>,
|
||||||
|
#[arg] set: Option<Governor>,
|
||||||
|
) -> Result<GovernorInfo, Error> {
|
||||||
|
let available = get_available_governors().await?;
|
||||||
|
if let Some(set) = set {
|
||||||
|
if !available.contains(&set) {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("Governor {set} not available"),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
set_governor(&set).await?;
|
||||||
|
ctx.db
|
||||||
|
.mutate(|d| d.as_server_info_mut().as_governor_mut().ser(&Some(set)))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
let current = ctx.db.peek().await.as_server_info().as_governor().de()?;
|
||||||
|
Ok(GovernorInfo { current, available })
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct TimeInfo {
|
pub struct TimeInfo {
|
||||||
now: String,
|
now: String,
|
||||||
@@ -841,6 +893,7 @@ async fn get_disk_info() -> Result<MetricsDisk, Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[ignore]
|
||||||
pub async fn test_get_temp() {
|
pub async fn test_get_temp() {
|
||||||
println!("{}", get_temp().await.unwrap())
|
println!("{}", get_temp().await.unwrap())
|
||||||
}
|
}
|
||||||
@@ -856,6 +909,7 @@ pub async fn test_get_mem_info() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[ignore]
|
||||||
pub async fn test_get_disk_usage() {
|
pub async fn test_get_disk_usage() {
|
||||||
println!("{:?}", get_disk_info().await.unwrap())
|
println!("{:?}", get_disk_info().await.unwrap())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,20 @@ use tokio::process::Command;
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
|
|
||||||
pub const GOVERNOR_PERFORMANCE: Governor = Governor(Cow::Borrowed("performance"));
|
pub const GOVERNOR_HEIRARCHY: &[Governor] = &[
|
||||||
|
Governor(Cow::Borrowed("ondemand")),
|
||||||
|
Governor(Cow::Borrowed("schedutil")),
|
||||||
|
Governor(Cow::Borrowed("conservative")),
|
||||||
|
];
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct Governor(Cow<'static, str>);
|
pub struct Governor(Cow<'static, str>);
|
||||||
|
impl std::str::FromStr for Governor {
|
||||||
|
type Err = std::convert::Infallible;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Self(s.to_owned().into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
impl std::fmt::Display for Governor {
|
impl std::fmt::Display for Governor {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
self.0.fmt(f)
|
self.0.fmt(f)
|
||||||
@@ -29,13 +39,12 @@ impl std::borrow::Borrow<str> for Governor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_available_governors() -> Result<BTreeSet<Governor>, Error> {
|
pub async fn get_available_governors() -> Result<BTreeSet<Governor>, Error> {
|
||||||
let raw = String::from_utf8(
|
let raw = Command::new("cpupower")
|
||||||
Command::new("cpupower")
|
|
||||||
.arg("frequency-info")
|
.arg("frequency-info")
|
||||||
.arg("-g")
|
.arg("-g")
|
||||||
.invoke(ErrorKind::CpuSettings)
|
.invoke(ErrorKind::CpuSettings)
|
||||||
.await?,
|
.await
|
||||||
)?;
|
.map_or_else(|e| Ok(e.source.to_string()), String::from_utf8)?;
|
||||||
let mut for_cpu: OrdMap<u32, BTreeSet<Governor>> = OrdMap::new();
|
let mut for_cpu: OrdMap<u32, BTreeSet<Governor>> = OrdMap::new();
|
||||||
let mut current_cpu = None;
|
let mut current_cpu = None;
|
||||||
for line in raw.lines() {
|
for line in raw.lines() {
|
||||||
@@ -114,6 +123,16 @@ pub async fn current_governor() -> Result<Option<Governor>, Error> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_preferred_governor() -> Result<Option<&'static Governor>, Error> {
|
||||||
|
let governors = get_available_governors().await?;
|
||||||
|
for governor in GOVERNOR_HEIRARCHY {
|
||||||
|
if governors.contains(governor) {
|
||||||
|
return Ok(Some(governor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn set_governor(governor: &Governor) -> Result<(), Error> {
|
pub async fn set_governor(governor: &Governor) -> Result<(), Error> {
|
||||||
Command::new("cpupower")
|
Command::new("cpupower")
|
||||||
.arg("frequency-set")
|
.arg("frequency-set")
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ mod v0_3_4_2;
|
|||||||
mod v0_3_4_3;
|
mod v0_3_4_3;
|
||||||
mod v0_3_4_4;
|
mod v0_3_4_4;
|
||||||
mod v0_3_5;
|
mod v0_3_5;
|
||||||
|
mod v0_3_5_1;
|
||||||
|
|
||||||
pub type Current = v0_3_5::Version;
|
pub type Current = v0_3_5_1::Version;
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
@@ -26,6 +27,7 @@ enum Version {
|
|||||||
V0_3_4_3(Wrapper<v0_3_4_3::Version>),
|
V0_3_4_3(Wrapper<v0_3_4_3::Version>),
|
||||||
V0_3_4_4(Wrapper<v0_3_4_4::Version>),
|
V0_3_4_4(Wrapper<v0_3_4_4::Version>),
|
||||||
V0_3_5(Wrapper<v0_3_5::Version>),
|
V0_3_5(Wrapper<v0_3_5::Version>),
|
||||||
|
V0_3_5_1(Wrapper<v0_3_5_1::Version>),
|
||||||
Other(emver::Version),
|
Other(emver::Version),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +49,7 @@ impl Version {
|
|||||||
Version::V0_3_4_3(Wrapper(x)) => x.semver(),
|
Version::V0_3_4_3(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_3_4_4(Wrapper(x)) => x.semver(),
|
Version::V0_3_4_4(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_3_5(Wrapper(x)) => x.semver(),
|
Version::V0_3_5(Wrapper(x)) => x.semver(),
|
||||||
|
Version::V0_3_5_1(Wrapper(x)) => x.semver(),
|
||||||
Version::Other(x) => x.clone(),
|
Version::Other(x) => x.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,6 +175,7 @@ pub async fn init(db: &PatchDb, secrets: &PgPool) -> Result<(), Error> {
|
|||||||
Version::V0_3_4_3(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?,
|
Version::V0_3_4_3(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?,
|
||||||
Version::V0_3_4_4(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?,
|
Version::V0_3_4_4(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?,
|
||||||
Version::V0_3_5(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?,
|
Version::V0_3_5(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?,
|
||||||
|
Version::V0_3_5_1(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?,
|
||||||
Version::Other(_) => {
|
Version::Other(_) => {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("Cannot downgrade"),
|
eyre!("Cannot downgrade"),
|
||||||
@@ -208,6 +212,9 @@ mod tests {
|
|||||||
Just(Version::V0_3_4_1(Wrapper(v0_3_4_1::Version::new()))),
|
Just(Version::V0_3_4_1(Wrapper(v0_3_4_1::Version::new()))),
|
||||||
Just(Version::V0_3_4_2(Wrapper(v0_3_4_2::Version::new()))),
|
Just(Version::V0_3_4_2(Wrapper(v0_3_4_2::Version::new()))),
|
||||||
Just(Version::V0_3_4_3(Wrapper(v0_3_4_3::Version::new()))),
|
Just(Version::V0_3_4_3(Wrapper(v0_3_4_3::Version::new()))),
|
||||||
|
Just(Version::V0_3_4_4(Wrapper(v0_3_4_4::Version::new()))),
|
||||||
|
Just(Version::V0_3_5(Wrapper(v0_3_5::Version::new()))),
|
||||||
|
Just(Version::V0_3_5_1(Wrapper(v0_3_5_1::Version::new()))),
|
||||||
em_version().prop_map(Version::Other),
|
em_version().prop_map(Version::Other),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
32
core/startos/src/version/v0_3_5_1.rs
Normal file
32
core/startos/src/version/v0_3_5_1.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use emver::VersionRange;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
|
||||||
|
use super::v0_3_4::V0_3_0_COMPAT;
|
||||||
|
use super::{v0_3_5, VersionT};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
const V0_3_5_1: emver::Version = emver::Version::new(0, 3, 5, 1);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Version;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl VersionT for Version {
|
||||||
|
type Previous = v0_3_5::Version;
|
||||||
|
fn new() -> Self {
|
||||||
|
Version
|
||||||
|
}
|
||||||
|
fn semver(&self) -> emver::Version {
|
||||||
|
V0_3_5_1
|
||||||
|
}
|
||||||
|
fn compat(&self) -> &'static VersionRange {
|
||||||
|
&V0_3_0_COMPAT
|
||||||
|
}
|
||||||
|
async fn up(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
async fn down(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
28
download-firmware.sh
Executable file
28
download-firmware.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PLATFORM=$1
|
||||||
|
|
||||||
|
if [ -z "$PLATFORM" ]; then
|
||||||
|
>&2 echo "usage: $0 <PLATFORM>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf ./firmware/$PLATFORM
|
||||||
|
mkdir -p ./firmware/$PLATFORM
|
||||||
|
|
||||||
|
cd ./firmware/$PLATFORM
|
||||||
|
|
||||||
|
mapfile -t firmwares <<< "$(jq -c ".[] | select(.platform[] | contains(\"$PLATFORM\"))" ../../build/lib/firmware.json)"
|
||||||
|
for firmware in "${firmwares[@]}"; do
|
||||||
|
if [ -n "$firmware" ]; then
|
||||||
|
id=$(echo "$firmware" | jq --raw-output '.id')
|
||||||
|
url=$(echo "$firmware" | jq --raw-output '.url')
|
||||||
|
shasum=$(echo "$firmware" | jq --raw-output '.shasum')
|
||||||
|
curl --fail -L -o "${id}.rom.gz" "$url"
|
||||||
|
echo "$shasum ${id}.rom.gz" | sha256sum -c
|
||||||
|
fi
|
||||||
|
done
|
||||||
@@ -158,8 +158,8 @@ echo "deb [arch=${IB_TARGET_ARCH} signed-by=/etc/apt/trusted.gpg.d/tor.key.gpg]
|
|||||||
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o config/archives/docker.key
|
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o config/archives/docker.key
|
||||||
echo "deb [arch=${IB_TARGET_ARCH} signed-by=/etc/apt/trusted.gpg.d/docker.key.gpg] https://download.docker.com/linux/debian ${IB_SUITE} stable" > config/archives/docker.list
|
echo "deb [arch=${IB_TARGET_ARCH} signed-by=/etc/apt/trusted.gpg.d/docker.key.gpg] https://download.docker.com/linux/debian ${IB_SUITE} stable" > config/archives/docker.list
|
||||||
|
|
||||||
curl -fsSL https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/Debian_Testing/Release.key | gpg --dearmor -o config/archives/podman.key
|
curl -fsSL https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/Debian_Testing/Release.key | gpg --dearmor -o config/archives/podman.key
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/podman.key.gpg] https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/Debian_Testing/ /" > config/archives/podman.list
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/podman.key.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/unstable/Debian_Testing/ /" > config/archives/podman.list
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
|
|||||||
10
system-images/compat/Cargo.lock
generated
10
system-images/compat/Cargo.lock
generated
@@ -3800,9 +3800,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.16"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
|
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
@@ -4399,7 +4402,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "start-os"
|
name = "start-os"
|
||||||
version = "0.3.5"
|
version = "0.3.5-rev.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
@@ -4475,6 +4478,7 @@ dependencies = [
|
|||||||
"rpc-toolkit",
|
"rpc-toolkit",
|
||||||
"rust-argon2 2.0.0",
|
"rust-argon2 2.0.0",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
|||||||
4
web/package-lock.json
generated
4
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.3.5",
|
"version": "0.3.5.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.3.5",
|
"version": "0.3.5.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^14.1.0",
|
"@angular/animations": "^14.1.0",
|
||||||
"@angular/common": "^14.1.0",
|
"@angular/common": "^14.1.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.3.5",
|
"version": "0.3.5.1",
|
||||||
"author": "Start9 Labs, Inc",
|
"author": "Start9 Labs, Inc",
|
||||||
"homepage": "https://start9.com/",
|
"homepage": "https://start9.com/",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": null,
|
"name": null,
|
||||||
"ack-welcome": "0.3.5",
|
"ack-welcome": "0.3.5.1",
|
||||||
"marketplace": {
|
"marketplace": {
|
||||||
"selected-url": "https://registry.start9.com/",
|
"selected-url": "https://registry.start9.com/",
|
||||||
"known-hosts": {
|
"known-hosts": {
|
||||||
|
|||||||
@@ -12,6 +12,26 @@
|
|||||||
<ion-content class="ion-padding">
|
<ion-content class="ion-padding">
|
||||||
<h2>This Release</h2>
|
<h2>This Release</h2>
|
||||||
|
|
||||||
|
<h4>0.3.5.1</h4>
|
||||||
|
<p class="note-padding">
|
||||||
|
View the complete
|
||||||
|
<a
|
||||||
|
href="https://github.com/Start9Labs/start-os/releases/tag/v0.3.5.1"
|
||||||
|
target="_blank"
|
||||||
|
noreferrer
|
||||||
|
>
|
||||||
|
release notes
|
||||||
|
</a>
|
||||||
|
for more details.
|
||||||
|
</p>
|
||||||
|
<h6>Highlights</h6>
|
||||||
|
<ul class="spaced-list">
|
||||||
|
<li>Revert perpetual performance mode for quieter fan</li>
|
||||||
|
<li>Minor bug fixes</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Previous 0.3.5.x Releases</h2>
|
||||||
|
|
||||||
<h4>0.3.5</h4>
|
<h4>0.3.5</h4>
|
||||||
<p class="note-padding">
|
<p class="note-padding">
|
||||||
View the complete
|
View the complete
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
size="small"
|
size="small"
|
||||||
shape="round"
|
shape="round"
|
||||||
color="primary"
|
color="primary"
|
||||||
href="https://docs.start9.com/0.3.5.x/user-manual/trust-ca#establishing-trust"
|
href="https://docs.start9.com/0.3.5.x/user-manual/trust-ca"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
noreferrer
|
noreferrer
|
||||||
>
|
>
|
||||||
@@ -100,7 +100,4 @@
|
|||||||
<a
|
<a
|
||||||
id="install-cert"
|
id="install-cert"
|
||||||
href="/eos/local.crt"
|
href="/eos/local.crt"
|
||||||
[download]="
|
|
||||||
config.isLocal() ? document.location.hostname + '.crt' : 'startos.crt'
|
|
||||||
"
|
|
||||||
></a>
|
></a>
|
||||||
|
|||||||
@@ -27,8 +27,8 @@
|
|||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ server.zram ? 'Disable' : 'Enable' }} zram</h2>
|
<h2>{{ server.zram ? 'Disable' : 'Enable' }} zram</h2>
|
||||||
<p>
|
<p>
|
||||||
Enabling zram may improve server performance, especially on low RAM
|
Zram creates compressed swap in memory, resulting in faster I/O for
|
||||||
devices
|
low RAM devices
|
||||||
</p>
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -65,10 +65,10 @@ export class ExperimentalFeaturesPage {
|
|||||||
|
|
||||||
async presentAlertZram(enabled: boolean) {
|
async presentAlertZram(enabled: boolean) {
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: enabled ? 'Confirm' : 'Warning',
|
header: 'Confirm',
|
||||||
message: enabled
|
message: enabled
|
||||||
? 'Are you sure you want to disable zram?'
|
? 'Are you sure you want to disable zram? It provides significant performance benefits on low RAM devices.'
|
||||||
: 'zram on StartOS is experimental. It may increase performance of you server, especially if it is a low RAM device.',
|
: 'Enable zram? It will only make a difference on lower RAM devices.',
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: 'Cancel',
|
text: 'Cancel',
|
||||||
@@ -82,7 +82,6 @@ export class ExperimentalFeaturesPage {
|
|||||||
cssClass: 'enter-click',
|
cssClass: 'enter-click',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
cssClass: enabled ? '' : 'alert-warning-message',
|
|
||||||
})
|
})
|
||||||
await alert.present()
|
await alert.present()
|
||||||
}
|
}
|
||||||
@@ -122,7 +121,7 @@ export class ExperimentalFeaturesPage {
|
|||||||
|
|
||||||
private async toggleZram(enabled: boolean) {
|
private async toggleZram(enabled: boolean) {
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
message: enabled ? 'Disabling zram...' : 'Enabling zram',
|
message: enabled ? 'Disabling zram...' : 'Enabling zram...',
|
||||||
})
|
})
|
||||||
await loader.present()
|
await loader.present()
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<h2>
|
<h2>
|
||||||
For a secure local connection and faster Tor experience,
|
For a secure local connection and faster Tor experience,
|
||||||
<a
|
<a
|
||||||
href="https://docs.start9.com/0.3.5.x/user-manual/connecting-lan"
|
href="https://docs.start9.com/0.3.5.x/user-manual/trust-ca"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item button (click)="installCert()" [disabled]="!(crtName$ | async)">
|
<ion-item button (click)="installCert()">
|
||||||
<ion-icon slot="start" name="download-outline" size="large"></ion-icon>
|
<ion-icon slot="start" name="download-outline" size="large"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h1>Download Root CA</h1>
|
<h1>Download Root CA</h1>
|
||||||
@@ -35,5 +35,5 @@
|
|||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
<!-- hidden element for downloading cert -->
|
<!-- hidden element for downloading cert -->
|
||||||
<a id="install-cert" href="/eos/local.crt" [download]="crtName$ | async"></a>
|
<a id="install-cert" href="/eos/local.crt"></a>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class LANPage {
|
export class LANPage {
|
||||||
readonly crtName$ = this.patch
|
|
||||||
.watch$('server-info', 'lan-address')
|
|
||||||
.pipe(map(addr => `${new URL(addr).hostname}.crt`))
|
|
||||||
|
|
||||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||||
|
|
||||||
installCert(): void {
|
installCert(): void {
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ export module Mock {
|
|||||||
'shutting-down': false,
|
'shutting-down': false,
|
||||||
}
|
}
|
||||||
export const MarketplaceEos: RR.GetMarketplaceEosRes = {
|
export const MarketplaceEos: RR.GetMarketplaceEosRes = {
|
||||||
version: '0.3.5',
|
version: '0.3.5.1',
|
||||||
headline: 'Our biggest release ever.',
|
headline: 'Our biggest release ever.',
|
||||||
'release-notes': {
|
'release-notes': {
|
||||||
'0.3.5': 'Some **Markdown** release _notes_ for 0.3.5',
|
'0.3.5.1': 'Some **Markdown** release _notes_ for 0.3.5.1',
|
||||||
'0.3.4.4': 'Some **Markdown** release _notes_ for 0.3.4.4',
|
'0.3.4.4': 'Some **Markdown** release _notes_ for 0.3.4.4',
|
||||||
'0.3.4.3': 'Some **Markdown** release _notes_ for 0.3.4.3',
|
'0.3.4.3': 'Some **Markdown** release _notes_ for 0.3.4.3',
|
||||||
'0.3.4.2': 'Some **Markdown** release _notes_ for 0.3.4.2',
|
'0.3.4.2': 'Some **Markdown** release _notes_ for 0.3.4.2',
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
'server-info': {
|
'server-info': {
|
||||||
id: 'abcdefgh',
|
id: 'abcdefgh',
|
||||||
version: '0.3.5',
|
version: '0.3.5.1',
|
||||||
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
|
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
|
||||||
'lan-address': 'https://adjective-noun.local',
|
'lan-address': 'https://adjective-noun.local',
|
||||||
'tor-address': 'https://myveryownspecialtoraddress.onion',
|
'tor-address': 'https://myveryownspecialtoraddress.onion',
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
"background_color": "#1e1e1e",
|
"background_color": "#1e1e1e",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"scope": ".",
|
"scope": ".",
|
||||||
"start_url": "/?version=035",
|
"start_url": "/?version=0351",
|
||||||
"id": "/?version=035",
|
"id": "/?version=0351",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "assets/img/icon.png",
|
"src": "assets/img/icon.png",
|
||||||
|
|||||||
Reference in New Issue
Block a user