mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
Refactor/patch db (#2415)
* the only way to begin is by beginning * chore: Convert over 3444 migration * fix imports * wip * feat: convert volume * convert: system.rs * wip(convert): Setup * wip properties * wip notifications * wip * wip migration * wip init * wip auth/control * wip action * wip control * wiip 034 * wip 344 * wip some more versions converted * feat: Reserialize the version of the db * wip rest of the versions * wip s9pk/manifest * wip wifi * chore: net/keys * chore: net/dns * wip net/dhcp * wip manager manager-map * gut dependency errors * wip update/mod * detect breakages locally for updates * wip: manager/mod * wip: manager/health * wip: backup/target/mod * fix: Typo addresses * clean control.rs * fix system package id * switch to btreemap for now * config wip * wip manager/mod * install wip Co-authored-by: J H <Blu-J@users.noreply.github.com> * chore: Update the last of the errors * feat: Change the prelude de to borrow * feat: Adding in some more things * chore: add to the prelude * chore: Small fixes * chore: Fixing the small errors * wip: Cleaning up check errors * wip: Fix some of the issues * chore: Fix setup * chore:fix version * chore: prelude, mod, http_reader * wip backup_bulk * chore: Last of the errors * upadte package.json * chore: changes needed for a build * chore: Removing some of the linting errors in the manager * chore: Some linting 101 * fix: Wrong order of who owns what * chore: Remove the unstable * chore: Remove the test in the todo * @dr-bonez did a refactoring on the backup * chore: Make sure that there can only be one override guard at a time * resolve most todos * wip: Add some more tracing to debug an error * wip: Use a mv instead of rename * wip: Revert some of the missing code segments found earlier * chore: Make the build * chore: Something about the lib looks like it iis broken * wip: More instrument and dev working * kill netdummy before creating it * better db analysis tools * fixes from testing * fix: Make add start the service * fix status after install * make wormhole * fix missing icon file * fix data url for icons * fix: Bad deser * bugfixes * fix: Backup * fix: Some of the restor * fix: Restoring works * update frontend patch-db types * hack it in (#2424) * hack it in * optimize * slightly cleaner * handle config pointers * dependency config errs * fix compat * cache docker * fix dependency expectation * fix dependency auto-config --------- Co-authored-by: Aiden McClelland <me@drbonez.dev> Co-authored-by: Matt Hill <mattnine@protonmail.com> Co-authored-by: J H <Blu-J@users.noreply.github.com> Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
This commit is contained in:
5
.github/workflows/startos-iso.yaml
vendored
5
.github/workflows/startos-iso.yaml
vendored
@@ -106,6 +106,11 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.NODEJS_VERSION }}
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: /var/lib/docker
|
||||
key: ${{ runner.os }}-${{ matrix.platform }}-docker-cache
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache-dir
|
||||
run: |
|
||||
|
||||
38
Makefile
38
Makefile
@@ -27,17 +27,24 @@ ifeq ($(REMOTE),)
|
||||
cp = cp -r $1 $2
|
||||
ln = ln -sf $1 $2
|
||||
else
|
||||
mkdir = ssh $(REMOTE) 'sudo mkdir -p $1'
|
||||
rm = ssh $(REMOTE) 'sudo rm -rf $1'
|
||||
ln = ssh $(REMOTE) 'sudo ln -sf $1 $2'
|
||||
ifeq ($(SSHPASS),)
|
||||
ssh = ssh $(REMOTE) $1
|
||||
else
|
||||
ssh = sshpass -p $(SSHPASS) ssh $(REMOTE) $1
|
||||
endif
|
||||
mkdir = $(call ssh,'sudo mkdir -p $1')
|
||||
rm = $(call ssh,'sudo rm -rf $1')
|
||||
ln = $(call ssh,'sudo ln -sf $1 $2')
|
||||
define cp
|
||||
$(TAR_BIN) --transform "s|^$1|x|" -czv -f- $1 | ssh $(REMOTE) "sudo tar --transform 's|^x|$2|' -xzv -f- -C /"
|
||||
$(TAR_BIN) --transform "s|^$1|x|" -czv -f- $1 | $(call ssh,"sudo tar --transform 's|^x|$2|' -xzv -f- -C /")
|
||||
endef
|
||||
endif
|
||||
|
||||
|
||||
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
.PHONY: all gzip install clean format sdk snapshots frontends ui backend reflash startos_raspberrypi.img sudo
|
||||
.PHONY: all gzip install clean format sdk snapshots frontends ui backend reflash startos_raspberrypi.img sudo wormhole
|
||||
|
||||
all: $(ALL_TARGETS)
|
||||
|
||||
@@ -104,21 +111,24 @@ update-overlay:
|
||||
@echo "\033[33mALL CHANGES WILL BE REVERTED IF YOU RESTART THE DEVICE\033[0m"
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
@if [ "`ssh $(REMOTE) 'cat /usr/lib/embassy/VERSION.txt'`" != "`cat ./VERSION.txt`" ]; then >&2 echo "StartOS requires migrations: update-overlay is unavailable." && false; fi
|
||||
ssh $(REMOTE) "sudo systemctl stop startd"
|
||||
$(MAKE) install REMOTE=$(REMOTE) OS_ARCH=$(OS_ARCH)
|
||||
ssh $(REMOTE) "sudo systemctl start startd"
|
||||
$(call ssh,"sudo systemctl stop startd")
|
||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) OS_ARCH=$(OS_ARCH)
|
||||
$(call ssh,"sudo systemctl start startd")
|
||||
|
||||
wormhole: backend/target/$(ARCH)-unknown-linux-gnu/release/startbox
|
||||
@wormhole send backend/target/$(ARCH)-unknown-linux-gnu/release/startbox 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo /usr/lib/embassy/scripts/chroot-and-upgrade \"cd /usr/bin && rm startbox && wormhole receive --accept-file %s && chmod +x startbox\"\n", $$3 }'
|
||||
|
||||
update:
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
ssh $(REMOTE) "sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/"
|
||||
$(MAKE) install REMOTE=$(REMOTE) DESTDIR=/media/embassy/next OS_ARCH=$(OS_ARCH)
|
||||
ssh $(REMOTE) "sudo touch /media/embassy/config/upgrade && sudo sync && sudo reboot"
|
||||
$(call ssh,"sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/")
|
||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/embassy/next OS_ARCH=$(OS_ARCH)
|
||||
$(call ssh,"sudo touch /media/embassy/config/upgrade && sudo sync && sudo reboot")
|
||||
|
||||
emulate-reflash:
|
||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||
ssh $(REMOTE) "sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/"
|
||||
$(MAKE) install REMOTE=$(REMOTE) DESTDIR=/media/embassy/next OS_ARCH=$(OS_ARCH)
|
||||
ssh $(REMOTE) "sudo touch /media/embassy/config/upgrade && sudo rm -f /media/embassy/config/disk.guid && sudo sync && sudo reboot"
|
||||
$(call ssh,"sudo rsync -a --delete --force --info=progress2 /media/embassy/embassyfs/current/ /media/embassy/next/")
|
||||
$(MAKE) install REMOTE=$(REMOTE) SSHPASS=$(SSHPASS) DESTDIR=/media/embassy/next OS_ARCH=$(OS_ARCH)
|
||||
$(call ssh,"sudo touch /media/embassy/config/upgrade && sudo rm -f /media/embassy/config/disk.guid && sudo sync && sudo reboot")
|
||||
|
||||
system-images/compat/docker-images/aarch64.tar system-images/compat/docker-images/x86_64.tar: $(COMPAT_SRC)
|
||||
cd system-images/compat && make
|
||||
|
||||
325
backend/Cargo.lock
generated
325
backend/Cargo.lock
generated
@@ -58,6 +58,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"getrandom 0.2.10",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
@@ -374,15 +375,6 @@ version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
|
||||
|
||||
[[package]]
|
||||
name = "bitmaps"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitmaps"
|
||||
version = "3.2.0"
|
||||
@@ -437,44 +429,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
|
||||
|
||||
[[package]]
|
||||
name = "bollard"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d82e7850583ead5f8bbef247e2a3c37a19bd576e8420cd262a6711921827e1e5"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"bollard-stubs",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"http",
|
||||
"hyper",
|
||||
"hyperlocal",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"url",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bollard-stubs"
|
||||
version = "1.42.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_with 1.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "3.3.4"
|
||||
@@ -664,7 +618,7 @@ dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-error 0.2.0",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -676,7 +630,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-core",
|
||||
"tracing-error 0.2.0",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -912,38 +866,14 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||
dependencies = [
|
||||
"darling_core 0.13.4",
|
||||
"darling_macro 0.13.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
|
||||
dependencies = [
|
||||
"darling_core 0.20.3",
|
||||
"darling_macro 0.20.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2 1.0.66",
|
||||
"quote 1.0.31",
|
||||
"strsim 0.10.0",
|
||||
"syn 1.0.109",
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -960,24 +890,13 @@ dependencies = [
|
||||
"syn 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||
dependencies = [
|
||||
"darling_core 0.13.4",
|
||||
"quote 1.0.31",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core 0.20.3",
|
||||
"darling_core",
|
||||
"quote 1.0.31",
|
||||
"syn 2.0.18",
|
||||
]
|
||||
@@ -1339,7 +1258,7 @@ dependencies = [
|
||||
"color-eyre",
|
||||
"futures",
|
||||
"helpers",
|
||||
"imbl 2.0.0",
|
||||
"imbl",
|
||||
"nix 0.25.1",
|
||||
"procfs",
|
||||
"serde",
|
||||
@@ -1347,9 +1266,9 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"tracing-error 0.2.0",
|
||||
"tracing-error",
|
||||
"tracing-futures",
|
||||
"tracing-subscriber 0.3.17",
|
||||
"tracing-subscriber",
|
||||
"yajrc 0.1.0 (git+https://github.com/dr-bonez/yajrc.git?branch=develop)",
|
||||
]
|
||||
|
||||
@@ -1400,7 +1319,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"heck",
|
||||
"proc-macro2 1.0.66",
|
||||
"quote 1.0.31",
|
||||
"syn 1.0.109",
|
||||
@@ -1821,6 +1740,15 @@ dependencies = [
|
||||
"ahash 0.7.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
@@ -1840,15 +1768,6 @@ dependencies = [
|
||||
"hashbrown 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@@ -2033,19 +1952,6 @@ dependencies = [
|
||||
"tokio-tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyperlocal"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"hex",
|
||||
"hyper",
|
||||
"pin-project",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.57"
|
||||
@@ -2112,30 +2018,17 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
||||
|
||||
[[package]]
|
||||
name = "imbl"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "543682c9082b25e63d03b5acbd65ad111fd49dd93e70843e5175db4ff81d606b"
|
||||
dependencies = [
|
||||
"bitmaps 2.1.0",
|
||||
"rand_core 0.6.4",
|
||||
"rand_xoshiro",
|
||||
"sized-chunks",
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imbl"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2806b69cd9f4664844027b64465eacb444c67c1db9c778e341adff0c25cdb0d"
|
||||
dependencies = [
|
||||
"bitmaps 3.2.0",
|
||||
"bitmaps",
|
||||
"imbl-sized-chunks",
|
||||
"rand_core 0.6.4",
|
||||
"rand_xoshiro",
|
||||
"serde",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
@@ -2145,7 +2038,19 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6957ea0b2541c5ca561d3ef4538044af79f8a05a1eb3a3b148936aaceaa1076"
|
||||
dependencies = [
|
||||
"bitmaps 3.2.0",
|
||||
"bitmaps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imbl-value"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Start9Labs/imbl-value.git#929395141c3a882ac366c12ac9402d0ebaa2201b"
|
||||
dependencies = [
|
||||
"imbl",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"treediff",
|
||||
"yasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2203,20 +2108,6 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "internment"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "161079c3ad892faa215fcfcf3fd7a6a3c9288df2b06a2c2bad7fbfad4f01d69d"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"dashmap",
|
||||
"hashbrown 0.12.3",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
@@ -2395,9 +2286,9 @@ dependencies = [
|
||||
name = "json-patch"
|
||||
version = "0.2.7-alpha.0"
|
||||
dependencies = [
|
||||
"imbl-value",
|
||||
"json-ptr",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"treediff",
|
||||
]
|
||||
|
||||
@@ -2405,17 +2296,18 @@ dependencies = [
|
||||
name = "json-ptr"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"imbl",
|
||||
"imbl-value",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonpath_lib"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f"
|
||||
source = "git+https://github.com/Start9Labs/jsonpath.git#1cacbd64afa2e1941a21fef06bad14317ba92f30"
|
||||
dependencies = [
|
||||
"imbl-value",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2701,6 +2593,15 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@@ -2737,11 +2638,10 @@ dependencies = [
|
||||
name = "models"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bollard",
|
||||
"base64 0.21.2",
|
||||
"color-eyre",
|
||||
"ed25519-dalek",
|
||||
"emver",
|
||||
"internment",
|
||||
"ipnet",
|
||||
"lazy_static",
|
||||
"mbrman",
|
||||
@@ -2749,6 +2649,7 @@ dependencies = [
|
||||
"patch-db",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"rpc-toolkit",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2758,6 +2659,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"torut",
|
||||
"tracing",
|
||||
"yasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2803,19 +2705,6 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.24.3"
|
||||
@@ -2825,7 +2714,7 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"memoffset 0.6.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2838,10 +2727,24 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"memoffset 0.6.5",
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"memoffset 0.7.1",
|
||||
"pin-utils",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.3"
|
||||
@@ -3211,19 +3114,19 @@ dependencies = [
|
||||
"async-trait",
|
||||
"fd-lock-rs",
|
||||
"futures",
|
||||
"imbl 1.0.1",
|
||||
"imbl",
|
||||
"imbl-value",
|
||||
"json-patch",
|
||||
"json-ptr",
|
||||
"lazy_static",
|
||||
"nix 0.23.2",
|
||||
"nix 0.26.2",
|
||||
"patch-db-macro",
|
||||
"serde",
|
||||
"serde_cbor 0.11.1",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-error 0.1.2",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3239,7 +3142,7 @@ dependencies = [
|
||||
name = "patch-db-macro-internals"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck 0.3.3",
|
||||
"heck",
|
||||
"proc-macro2 1.0.66",
|
||||
"quote 1.0.31",
|
||||
"syn 1.0.109",
|
||||
@@ -4237,16 +4140,6 @@ dependencies = [
|
||||
"v8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_with_macros 1.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "2.3.3"
|
||||
@@ -4259,29 +4152,17 @@ dependencies = [
|
||||
"indexmap 1.9.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with_macros 2.3.3",
|
||||
"serde_with_macros",
|
||||
"time 0.3.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
|
||||
dependencies = [
|
||||
"darling 0.13.4",
|
||||
"proc-macro2 1.0.66",
|
||||
"quote 1.0.31",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f"
|
||||
dependencies = [
|
||||
"darling 0.20.3",
|
||||
"darling",
|
||||
"proc-macro2 1.0.66",
|
||||
"quote 1.0.31",
|
||||
"syn 2.0.18",
|
||||
@@ -4419,16 +4300,6 @@ version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
||||
|
||||
[[package]]
|
||||
name = "sized-chunks"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
|
||||
dependencies = [
|
||||
"bitmaps 2.1.0",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.8"
|
||||
@@ -4579,7 +4450,7 @@ checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
"heck 0.4.1",
|
||||
"heck",
|
||||
"hex",
|
||||
"once_cell",
|
||||
"proc-macro2 1.0.66",
|
||||
@@ -4690,7 +4561,8 @@ dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-ws-listener",
|
||||
"imbl 2.0.0",
|
||||
"imbl",
|
||||
"imbl-value",
|
||||
"include_dir",
|
||||
"indexmap 1.9.3",
|
||||
"ipnet",
|
||||
@@ -4733,7 +4605,7 @@ dependencies = [
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with 2.3.3",
|
||||
"serde_with",
|
||||
"serde_yaml",
|
||||
"sha2 0.10.7",
|
||||
"sha2 0.9.9",
|
||||
@@ -4753,9 +4625,9 @@ dependencies = [
|
||||
"toml",
|
||||
"torut",
|
||||
"tracing",
|
||||
"tracing-error 0.2.0",
|
||||
"tracing-error",
|
||||
"tracing-futures",
|
||||
"tracing-subscriber 0.3.17",
|
||||
"tracing-subscriber",
|
||||
"trust-dns-server",
|
||||
"typed-builder",
|
||||
"url",
|
||||
@@ -4859,7 +4731,7 @@ version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"heck",
|
||||
"proc-macro2 1.0.66",
|
||||
"quote 1.0.31",
|
||||
"rustversion",
|
||||
@@ -5627,16 +5499,6 @@ dependencies = [
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-error"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber 0.2.25",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-error"
|
||||
version = "0.2.0"
|
||||
@@ -5644,7 +5506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber 0.3.17",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5668,17 +5530,6 @@ dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
|
||||
dependencies = [
|
||||
"sharded-slab",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.17"
|
||||
@@ -6390,6 +6241,18 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yasi"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f355ab62ebe30b758c1f4ab096a306722c4b7dbfb9d8c07d18c70d71a945588"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"hashbrown 0.13.2",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.6.0"
|
||||
|
||||
@@ -78,6 +78,7 @@ http = "0.2.8"
|
||||
hyper = { version = "0.14.20", features = ["full"] }
|
||||
hyper-ws-listener = "0.2.0"
|
||||
imbl = "2.0.0"
|
||||
imbl-value = { git = "https://github.com/Start9Labs/imbl-value.git" }
|
||||
include_dir = "0.7.3"
|
||||
indexmap = { version = "1.9.1", features = ["serde"] }
|
||||
ipnet = { version = "2.7.1", features = ["serde"] }
|
||||
@@ -88,7 +89,7 @@ jaq-core = "0.10.0"
|
||||
jaq-std = "0.10.0"
|
||||
josekit = "0.8.1"
|
||||
js_engine = { path = '../libs/js_engine', optional = true }
|
||||
jsonpath_lib = "0.3.0"
|
||||
jsonpath_lib = { git = "https://github.com/Start9Labs/jsonpath.git" }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.126"
|
||||
log = "0.4.17"
|
||||
|
||||
@@ -11,6 +11,7 @@ use tracing::instrument;
|
||||
|
||||
use crate::config::{Config, ConfigSpec};
|
||||
use crate::context::RpcContext;
|
||||
use crate::prelude::*;
|
||||
use crate::procedure::docker::DockerContainers;
|
||||
use crate::procedure::{PackageProcedure, ProcedureName};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
@@ -59,7 +60,7 @@ impl Action {
|
||||
#[instrument(skip_all)]
|
||||
pub fn validate(
|
||||
&self,
|
||||
container: &Option<DockerContainers>,
|
||||
_container: &Option<DockerContainers>,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
@@ -130,18 +131,17 @@ pub async fn action(
|
||||
#[arg(long = "format")]
|
||||
format: Option<IoFormat>,
|
||||
) -> Result<ActionResult, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let manifest = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&pkg_id)
|
||||
.and_then(|p| p.installed())
|
||||
.expect(&mut db)
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::NotFound)?
|
||||
.manifest()
|
||||
.get(&mut db)
|
||||
let manifest = ctx
|
||||
.db
|
||||
.peek()
|
||||
.await?
|
||||
.to_owned();
|
||||
.as_package_data()
|
||||
.as_idx(&pkg_id)
|
||||
.or_not_found(&pkg_id)?
|
||||
.as_installed()
|
||||
.or_not_found(&pkg_id)?
|
||||
.as_manifest()
|
||||
.de()?;
|
||||
|
||||
if let Some(action) = manifest.actions.0.get(&action_id) {
|
||||
action
|
||||
|
||||
@@ -5,7 +5,6 @@ use chrono::{DateTime, Utc};
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use josekit::jwk::Jwk;
|
||||
use patch_db::{DbHandle, LockReceipt};
|
||||
use rpc_toolkit::command;
|
||||
use rpc_toolkit::command_helpers::prelude::{RequestParts, ResponseParts};
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
@@ -17,6 +16,7 @@ use tracing::instrument;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::middleware::auth::{AsLogoutSessionId, HasLoggedOutSessions, HashSessionToken};
|
||||
use crate::middleware::encrypt::EncryptedWire;
|
||||
use crate::prelude::*;
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::{display_serializable, IoFormat};
|
||||
use crate::{ensure_code, Error, ResultExt};
|
||||
@@ -343,27 +343,6 @@ async fn cli_reset_password(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct SetPasswordReceipt(LockReceipt<String, ()>);
|
||||
impl SetPasswordReceipt {
|
||||
pub async fn new<Db: DbHandle>(db: &mut Db) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let password_hash = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.password_hash()
|
||||
.make_locker(patch_db::LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| Ok(Self(password_hash.verify(skeleton_key)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[command(
|
||||
rename = "reset-password",
|
||||
custom_cli(cli_reset_password(async, context(CliContext))),
|
||||
@@ -389,13 +368,14 @@ pub async fn reset_password(
|
||||
}
|
||||
account.set_password(&new_password)?;
|
||||
account.save(&ctx.secret_store).await?;
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.password_hash()
|
||||
.put(&mut ctx.db.handle(), &account.password)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
let account_password = &account.password;
|
||||
ctx.db
|
||||
.mutate(|d| {
|
||||
d.as_server_info_mut()
|
||||
.as_password_hash_mut()
|
||||
.ser(account_password)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[command(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::BTreeMap;
|
||||
use std::panic::UnwindSafe;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -6,9 +7,11 @@ use chrono::Utc;
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::AtomicFile;
|
||||
use patch_db::{DbHandle, LockType, PatchDbHandle};
|
||||
use imbl::OrdSet;
|
||||
use models::Version;
|
||||
use rpc_toolkit::command;
|
||||
use tokio::{io::AsyncWriteExt, sync::Mutex};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::target::BackupTargetId;
|
||||
@@ -18,26 +21,27 @@ use crate::backup::os::OsBackup;
|
||||
use crate::backup::{BackupReport, ServerBackupReport};
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::BackupProgress;
|
||||
use crate::db::package::get_packages;
|
||||
use crate::disk::mount::backup::BackupMountGuard;
|
||||
use crate::disk::mount::filesystem::ReadWrite;
|
||||
use crate::disk::mount::guard::TmpMountGuard;
|
||||
use crate::manager::BackupReturn;
|
||||
use crate::notifications::NotificationLevel;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::display_none;
|
||||
use crate::util::io::dir_copy;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::version::VersionT;
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result<BTreeSet<PackageId>, Error> {
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result<OrdSet<PackageId>, Error> {
|
||||
arg.split(',')
|
||||
.map(|s| s.trim().parse().map_err(Error::from))
|
||||
.map(|s| s.trim().parse::<PackageId>().map_err(Error::from))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[command(rename = "create", display(display_none))]
|
||||
#[instrument(skip_all)]
|
||||
#[instrument(skip(ctx, old_password, password))]
|
||||
pub async fn backup_all(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg(rename = "target-id")] target_id: BackupTargetId,
|
||||
@@ -49,10 +53,10 @@ pub async fn backup_all(
|
||||
long = "package-ids",
|
||||
parse(parse_comma_separated)
|
||||
)]
|
||||
package_ids: Option<BTreeSet<PackageId>>,
|
||||
package_ids: Option<OrdSet<PackageId>>,
|
||||
#[arg] password: crate::auth::PasswordType,
|
||||
) -> Result<(), Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let db = ctx.db.peek().await?;
|
||||
let old_password_decrypted = old_password
|
||||
.as_ref()
|
||||
.unwrap_or(&password)
|
||||
@@ -68,36 +72,33 @@ pub async fn backup_all(
|
||||
&old_password_decrypted,
|
||||
)
|
||||
.await?;
|
||||
let all_packages = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.get(&mut db)
|
||||
.await?
|
||||
.0
|
||||
.keys()
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let package_ids = package_ids.unwrap_or(all_packages);
|
||||
let package_ids = if let Some(ids) = package_ids {
|
||||
ids.into_iter()
|
||||
.flat_map(|package_id| {
|
||||
let version = db
|
||||
.as_package_data()
|
||||
.as_idx(&package_id)?
|
||||
.as_manifest()
|
||||
.as_version()
|
||||
.de()
|
||||
.ok()?;
|
||||
Some((package_id, version))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
get_packages(db.clone())?.into_iter().collect()
|
||||
};
|
||||
if old_password.is_some() {
|
||||
backup_guard.change_password(&password)?;
|
||||
}
|
||||
assure_backing_up(&mut db, &package_ids).await?;
|
||||
assure_backing_up(&ctx.db, &package_ids).await?;
|
||||
tokio::task::spawn(async move {
|
||||
let backup_res = perform_backup(&ctx, &mut db, backup_guard, &package_ids).await;
|
||||
let backup_progress = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.status_info()
|
||||
.backup_progress();
|
||||
backup_progress
|
||||
.clone()
|
||||
.lock(&mut db, LockType::Write)
|
||||
.await
|
||||
.expect("failed to lock server status");
|
||||
let backup_res = perform_backup(&ctx, backup_guard, &package_ids).await;
|
||||
match backup_res {
|
||||
Ok(report) if report.iter().all(|(_, rep)| rep.error.is_none()) => ctx
|
||||
.notification_manager
|
||||
.notify(
|
||||
&mut db,
|
||||
ctx.db.clone(),
|
||||
None,
|
||||
NotificationLevel::Success,
|
||||
"Backup Complete".to_owned(),
|
||||
@@ -107,7 +108,10 @@ pub async fn backup_all(
|
||||
attempted: true,
|
||||
error: None,
|
||||
},
|
||||
packages: report,
|
||||
packages: report
|
||||
.into_iter()
|
||||
.map(|((package_id, _), value)| (package_id, value))
|
||||
.collect(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
@@ -116,7 +120,7 @@ pub async fn backup_all(
|
||||
Ok(report) => ctx
|
||||
.notification_manager
|
||||
.notify(
|
||||
&mut db,
|
||||
ctx.db.clone(),
|
||||
None,
|
||||
NotificationLevel::Warning,
|
||||
"Backup Complete".to_owned(),
|
||||
@@ -126,7 +130,10 @@ pub async fn backup_all(
|
||||
attempted: true,
|
||||
error: None,
|
||||
},
|
||||
packages: report,
|
||||
packages: report
|
||||
.into_iter()
|
||||
.map(|((package_id, _), value)| (package_id, value))
|
||||
.collect(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
@@ -137,7 +144,7 @@ pub async fn backup_all(
|
||||
tracing::debug!("{:?}", e);
|
||||
ctx.notification_manager
|
||||
.notify(
|
||||
&mut db,
|
||||
ctx.db.clone(),
|
||||
None,
|
||||
NotificationLevel::Error,
|
||||
"Backup Failed".to_owned(),
|
||||
@@ -155,106 +162,85 @@ pub async fn backup_all(
|
||||
.expect("failed to send notification");
|
||||
}
|
||||
}
|
||||
backup_progress
|
||||
.delete(&mut db)
|
||||
.await
|
||||
.expect("failed to change server status");
|
||||
ctx.db
|
||||
.mutate(|v| {
|
||||
v.as_server_info_mut()
|
||||
.as_status_info_mut()
|
||||
.as_backup_progress_mut()
|
||||
.ser(&None)
|
||||
})
|
||||
.await?;
|
||||
Ok::<(), Error>(())
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
#[instrument(skip(db, packages))]
|
||||
async fn assure_backing_up(
|
||||
db: &mut PatchDbHandle,
|
||||
packages: impl IntoIterator<Item = &PackageId>,
|
||||
db: &PatchDb,
|
||||
packages: impl IntoIterator<Item = &(PackageId, Version)> + UnwindSafe + Send,
|
||||
) -> Result<(), Error> {
|
||||
let mut tx = db.begin().await?;
|
||||
let mut backing_up = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.status_info()
|
||||
.backup_progress()
|
||||
.get_mut(&mut tx)
|
||||
.await?;
|
||||
|
||||
if backing_up
|
||||
.iter()
|
||||
.flat_map(|x| x.values())
|
||||
.fold(false, |acc, x| {
|
||||
if !x.complete {
|
||||
return true;
|
||||
}
|
||||
acc
|
||||
})
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("Server is already backing up!"),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
*backing_up = Some(
|
||||
packages
|
||||
.into_iter()
|
||||
.map(|x| (x.clone(), BackupProgress { complete: false }))
|
||||
.collect(),
|
||||
);
|
||||
backing_up.save(&mut tx).await?;
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
db.mutate(|v| {
|
||||
let backing_up = v
|
||||
.as_server_info_mut()
|
||||
.as_status_info_mut()
|
||||
.as_backup_progress_mut();
|
||||
if backing_up
|
||||
.clone()
|
||||
.de()?
|
||||
.iter()
|
||||
.flat_map(|x| x.values())
|
||||
.fold(false, |acc, x| {
|
||||
if !x.complete {
|
||||
return true;
|
||||
}
|
||||
acc
|
||||
})
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("Server is already backing up!"),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
backing_up.ser(&Some(
|
||||
packages
|
||||
.into_iter()
|
||||
.map(|(x, _)| (x.clone(), BackupProgress { complete: false }))
|
||||
.collect(),
|
||||
))?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn perform_backup<Db: DbHandle>(
|
||||
#[instrument(skip(ctx, backup_guard))]
|
||||
async fn perform_backup(
|
||||
ctx: &RpcContext,
|
||||
mut db: Db,
|
||||
backup_guard: BackupMountGuard<TmpMountGuard>,
|
||||
package_ids: &BTreeSet<PackageId>,
|
||||
) -> Result<BTreeMap<PackageId, PackageBackupReport>, Error> {
|
||||
package_ids: &OrdSet<(PackageId, Version)>,
|
||||
) -> Result<BTreeMap<(PackageId, Version), PackageBackupReport>, Error> {
|
||||
let mut backup_report = BTreeMap::new();
|
||||
let backup_guard = Arc::new(Mutex::new(backup_guard));
|
||||
|
||||
for package_id in crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.keys(&mut db)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|id| package_ids.contains(id))
|
||||
{
|
||||
let mut tx = db.begin().await?; // for lock scope
|
||||
let installed_model = if let Some(installed_model) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&package_id)
|
||||
.and_then(|m| m.installed())
|
||||
.check(&mut tx)
|
||||
.await?
|
||||
{
|
||||
installed_model
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let main_status_model = installed_model.clone().status().main();
|
||||
|
||||
let manifest = installed_model.clone().manifest().get(&mut tx).await?;
|
||||
|
||||
let (response, report) = match ctx
|
||||
for package_id in package_ids {
|
||||
let (response, _report) = match ctx
|
||||
.managers
|
||||
.get(&(manifest.id.clone(), manifest.version.clone()))
|
||||
.get(package_id)
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest)
|
||||
})?
|
||||
.ok_or_else(|| Error::new(eyre!("Manager not found"), ErrorKind::InvalidRequest))?
|
||||
.backup(backup_guard.clone())
|
||||
.await
|
||||
{
|
||||
BackupReturn::Ran { report, res } => (res, report),
|
||||
BackupReturn::AlreadyRunning(report) => {
|
||||
backup_report.insert(package_id, report);
|
||||
backup_report.insert(package_id.clone(), report);
|
||||
continue;
|
||||
}
|
||||
BackupReturn::Error(error) => {
|
||||
tracing::warn!("Backup thread error");
|
||||
tracing::debug!("{error:?}");
|
||||
backup_report.insert(
|
||||
package_id,
|
||||
package_id.clone(),
|
||||
PackageBackupReport {
|
||||
error: Some("Backup thread error".to_owned()),
|
||||
},
|
||||
@@ -270,42 +256,16 @@ async fn perform_backup<Db: DbHandle>(
|
||||
);
|
||||
|
||||
if let Ok(pkg_meta) = response {
|
||||
installed_model
|
||||
.last_backup()
|
||||
.put(&mut tx, &Some(pkg_meta.timestamp))
|
||||
.await?;
|
||||
backup_guard
|
||||
.lock()
|
||||
.await
|
||||
.metadata
|
||||
.package_backups
|
||||
.insert(package_id.clone(), pkg_meta);
|
||||
.insert(package_id.0.clone(), pkg_meta);
|
||||
}
|
||||
|
||||
let mut backup_progress = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.status_info()
|
||||
.backup_progress()
|
||||
.get_mut(&mut tx)
|
||||
.await?;
|
||||
if backup_progress.is_none() {
|
||||
*backup_progress = Some(Default::default());
|
||||
}
|
||||
if let Some(mut backup_progress) = backup_progress
|
||||
.as_mut()
|
||||
.and_then(|bp| bp.get_mut(&package_id))
|
||||
{
|
||||
(*backup_progress).complete = true;
|
||||
}
|
||||
backup_progress.save(&mut tx).await?;
|
||||
tx.save().await?;
|
||||
}
|
||||
|
||||
let ui = crate::db::DatabaseModel::new()
|
||||
.ui()
|
||||
.get(&mut db)
|
||||
.await?
|
||||
.into_owned();
|
||||
let ui = ctx.db.peek().await?.into_ui().de()?;
|
||||
|
||||
let mut os_backup_file = AtomicFile::new(
|
||||
backup_guard.lock().await.as_ref().join("os-backup.cbor"),
|
||||
@@ -354,10 +314,9 @@ async fn perform_backup<Db: DbHandle>(
|
||||
|
||||
backup_guard.save_and_unmount().await?;
|
||||
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.last_backup()
|
||||
.put(&mut db, ×tamp)
|
||||
ctx.db
|
||||
.mutate(|v| v.as_server_info_mut().as_last_backup_mut().ser(×tamp))
|
||||
.await?;
|
||||
|
||||
Ok(backup_report)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::AtomicFile;
|
||||
use models::ImageId;
|
||||
use patch_db::{DbHandle, HasModel};
|
||||
use models::{ImageId, OptionExt};
|
||||
use reqwest::Url;
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -15,10 +15,11 @@ use tracing::instrument;
|
||||
|
||||
use self::target::PackageBackupInfo;
|
||||
use crate::context::RpcContext;
|
||||
use crate::dependencies::reconfigure_dependents_with_live_pointers;
|
||||
use crate::install::PKG_ARCHIVE_DIR;
|
||||
use crate::net::interface::{InterfaceId, Interfaces};
|
||||
use crate::manager::manager_seed::ManagerSeed;
|
||||
use crate::net::interface::InterfaceId;
|
||||
use crate::net::keys::Key;
|
||||
use crate::prelude::*;
|
||||
use crate::procedure::docker::DockerContainers;
|
||||
use crate::procedure::{NoOutput, PackageProcedure, ProcedureName};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
@@ -71,6 +72,7 @@ struct BackupMetadata {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct BackupActions {
|
||||
pub create: PackageProcedure,
|
||||
pub restore: PackageProcedure,
|
||||
@@ -78,7 +80,7 @@ pub struct BackupActions {
|
||||
impl BackupActions {
|
||||
pub fn validate(
|
||||
&self,
|
||||
container: &Option<DockerContainers>,
|
||||
_container: &Option<DockerContainers>,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
@@ -93,19 +95,14 @@ impl BackupActions {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn create<Db: DbHandle>(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
pkg_id: &PackageId,
|
||||
pkg_title: &str,
|
||||
pkg_version: &Version,
|
||||
interfaces: &Interfaces,
|
||||
volumes: &Volumes,
|
||||
) -> Result<PackageBackupInfo, Error> {
|
||||
let mut volumes = volumes.to_readonly();
|
||||
pub async fn create(&self, seed: Arc<ManagerSeed>) -> Result<PackageBackupInfo, Error> {
|
||||
let manifest = &seed.manifest;
|
||||
let mut volumes = seed.manifest.volumes.to_readonly();
|
||||
let ctx = &seed.ctx;
|
||||
let pkg_id = &manifest.id;
|
||||
let pkg_version = &manifest.version;
|
||||
volumes.insert(VolumeId::Backup, Volume::Backup { readonly: false });
|
||||
let backup_dir = backup_dir(pkg_id);
|
||||
let backup_dir = backup_dir(&manifest.id);
|
||||
if tokio::fs::metadata(&backup_dir).await.is_err() {
|
||||
tokio::fs::create_dir_all(&backup_dir).await?
|
||||
}
|
||||
@@ -122,29 +119,29 @@ impl BackupActions {
|
||||
.await?
|
||||
.map_err(|e| eyre!("{}", e.1))
|
||||
.with_kind(crate::ErrorKind::Backup)?;
|
||||
let (network_keys, tor_keys) = Key::for_package(&ctx.secret_store, pkg_id)
|
||||
let (network_keys, tor_keys): (Vec<_>, Vec<_>) =
|
||||
Key::for_package(&ctx.secret_store, pkg_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|k| {
|
||||
let interface = k.interface().map(|(_, i)| i)?;
|
||||
Some((
|
||||
(interface.clone(), Base64(k.as_bytes())),
|
||||
(interface, Base32(k.tor_key().as_bytes())),
|
||||
))
|
||||
})
|
||||
.unzip();
|
||||
let marketplace_url = ctx
|
||||
.db
|
||||
.peek()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|k| {
|
||||
let interface = k.interface().map(|(_, i)| i)?;
|
||||
Some((
|
||||
(interface.clone(), Base64(k.as_bytes())),
|
||||
(interface, Base32(k.tor_key().as_bytes())),
|
||||
))
|
||||
})
|
||||
.unzip();
|
||||
let marketplace_url = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(pkg_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?
|
||||
.marketplace_url()
|
||||
.get(db)
|
||||
.await?
|
||||
.into_owned();
|
||||
.as_package_data()
|
||||
.as_idx(&pkg_id)
|
||||
.or_not_found(pkg_id)?
|
||||
.expect_as_installed()?
|
||||
.as_installed()
|
||||
.as_marketplace_url()
|
||||
.de()?;
|
||||
let tmp_path = Path::new(BACKUP_DIR)
|
||||
.join(pkg_id)
|
||||
.join(format!("{}.s9pk", pkg_id));
|
||||
@@ -172,6 +169,8 @@ impl BackupActions {
|
||||
let mut outfile = AtomicFile::new(&metadata_path, None::<PathBuf>)
|
||||
.await
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
let network_keys = network_keys.into_iter().collect();
|
||||
let tor_keys = tor_keys.into_iter().collect();
|
||||
outfile
|
||||
.write_all(&IoFormat::Cbor.to_vec(&BackupMetadata {
|
||||
timestamp,
|
||||
@@ -183,22 +182,20 @@ impl BackupActions {
|
||||
outfile.save().await.with_kind(ErrorKind::Filesystem)?;
|
||||
Ok(PackageBackupInfo {
|
||||
os_version: Current::new().semver().into(),
|
||||
title: pkg_title.to_owned(),
|
||||
title: manifest.title.clone(),
|
||||
version: pkg_version.clone(),
|
||||
timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn restore<Db: DbHandle>(
|
||||
pub async fn restore(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
interfaces: &Interfaces,
|
||||
volumes: &Volumes,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<Option<Url>, Error> {
|
||||
let mut volumes = volumes.clone();
|
||||
volumes.insert(VolumeId::Backup, Volume::Backup { readonly: true });
|
||||
self.restore
|
||||
@@ -223,32 +220,7 @@ impl BackupActions {
|
||||
)
|
||||
})?,
|
||||
)?;
|
||||
let pde = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(pkg_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?;
|
||||
pde.marketplace_url()
|
||||
.put(db, &metadata.marketplace_url)
|
||||
.await?;
|
||||
|
||||
let entry = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(pkg_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?
|
||||
.get(db)
|
||||
.await?;
|
||||
|
||||
let receipts = crate::config::ConfigReceipts::new(db).await?;
|
||||
reconfigure_dependents_with_live_pointers(ctx, db, &receipts, &entry).await?;
|
||||
|
||||
Ok(())
|
||||
Ok(metadata.marketplace_url)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use openssl::pkey::PKey;
|
||||
use openssl::x509::X509;
|
||||
use patch_db::Value;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::account::AccountInfo;
|
||||
use crate::hostname::{generate_hostname, generate_id, Hostname};
|
||||
use crate::net::keys::Key;
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::Base64;
|
||||
use crate::Error;
|
||||
|
||||
pub struct OsBackup {
|
||||
pub account: AccountInfo,
|
||||
@@ -20,11 +20,11 @@ impl<'de> Deserialize<'de> for OsBackup {
|
||||
{
|
||||
let tagged = OsBackupSerDe::deserialize(deserializer)?;
|
||||
match tagged.version {
|
||||
0 => serde_json::from_value::<OsBackupV0>(tagged.rest)
|
||||
0 => patch_db::value::from_value::<OsBackupV0>(tagged.rest)
|
||||
.map_err(serde::de::Error::custom)?
|
||||
.project()
|
||||
.map_err(serde::de::Error::custom),
|
||||
1 => serde_json::from_value::<OsBackupV1>(tagged.rest)
|
||||
1 => patch_db::value::from_value::<OsBackupV1>(tagged.rest)
|
||||
.map_err(serde::de::Error::custom)?
|
||||
.project()
|
||||
.map_err(serde::de::Error::custom),
|
||||
@@ -41,7 +41,7 @@ impl Serialize for OsBackup {
|
||||
{
|
||||
OsBackupSerDe {
|
||||
version: 1,
|
||||
rest: serde_json::to_value(
|
||||
rest: patch_db::value::to_value(
|
||||
&OsBackupV1::unproject(self).map_err(serde::ser::Error::custom)?,
|
||||
)
|
||||
.map_err(serde::ser::Error::custom)?,
|
||||
|
||||
@@ -5,11 +5,9 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{stream, FutureExt, StreamExt};
|
||||
use openssl::x509::X509;
|
||||
use patch_db::{DbHandle, PatchDbHandle};
|
||||
use rpc_toolkit::command;
|
||||
use sqlx::Connection;
|
||||
use tokio::fs::File;
|
||||
@@ -21,7 +19,7 @@ use crate::backup::os::OsBackup;
|
||||
use crate::backup::BackupMetadata;
|
||||
use crate::context::rpc::RpcContextConfig;
|
||||
use crate::context::{RpcContext, SetupContext};
|
||||
use crate::db::model::{PackageDataEntry, StaticFiles};
|
||||
use crate::db::model::{PackageDataEntry, PackageDataEntryRestoring, StaticFiles};
|
||||
use crate::disk::mount::backup::{BackupMountGuard, PackageBackupMountGuard};
|
||||
use crate::disk::mount::filesystem::ReadWrite;
|
||||
use crate::disk::mount::guard::TmpMountGuard;
|
||||
@@ -30,6 +28,7 @@ use crate::init::init;
|
||||
use crate::install::progress::InstallProgress;
|
||||
use crate::install::{download_install_s9pk, PKG_PUBLIC_DIR};
|
||||
use crate::notifications::NotificationLevel;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::s9pk::reader::S9pkReader;
|
||||
use crate::setup::SetupStatus;
|
||||
@@ -37,7 +36,6 @@ use crate::util::display_none;
|
||||
use crate::util::io::dir_size;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::volume::{backup_dir, BACKUP_DIR, PKG_VOLUME_DIR};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result<Vec<PackageId>, Error> {
|
||||
arg.split(',')
|
||||
@@ -46,33 +44,31 @@ fn parse_comma_separated(arg: &str, _: &ArgMatches) -> Result<Vec<PackageId>, Er
|
||||
}
|
||||
|
||||
#[command(rename = "restore", display(display_none))]
|
||||
#[instrument(skip_all)]
|
||||
#[instrument(skip(ctx, password))]
|
||||
pub async fn restore_packages_rpc(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg(parse(parse_comma_separated))] ids: Vec<PackageId>,
|
||||
#[arg(rename = "target-id")] target_id: BackupTargetId,
|
||||
#[arg] password: String,
|
||||
) -> Result<(), Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let fs = target_id
|
||||
.load(&mut ctx.secret_store.acquire().await?)
|
||||
.await?;
|
||||
let backup_guard =
|
||||
BackupMountGuard::mount(TmpMountGuard::mount(&fs, ReadWrite).await?, &password).await?;
|
||||
|
||||
let (backup_guard, tasks, _) = restore_packages(&ctx, &mut db, backup_guard, ids).await?;
|
||||
let (backup_guard, tasks, _) = restore_packages(&ctx, backup_guard, ids).await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
stream::iter(tasks.into_iter().map(|x| (x, ctx.clone())))
|
||||
.for_each_concurrent(5, |(res, ctx)| async move {
|
||||
let mut db = ctx.db.handle();
|
||||
match res.await {
|
||||
(Ok(_), _) => (),
|
||||
(Err(err), package_id) => {
|
||||
if let Err(err) = ctx
|
||||
.notification_manager
|
||||
.notify(
|
||||
&mut db,
|
||||
ctx.db.clone(),
|
||||
Some(package_id.clone()),
|
||||
NotificationLevel::Error,
|
||||
"Restoration Failure".to_string(),
|
||||
@@ -169,7 +165,7 @@ impl ProgressInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
#[instrument(skip(ctx))]
|
||||
pub async fn recover_full_embassy(
|
||||
ctx: SetupContext,
|
||||
disk_guid: Arc<String>,
|
||||
@@ -184,20 +180,18 @@ pub async fn recover_full_embassy(
|
||||
.await?;
|
||||
|
||||
let os_backup_path = backup_guard.as_ref().join("os-backup.cbor");
|
||||
let mut os_backup: OsBackup =
|
||||
IoFormat::Cbor.from_slice(&tokio::fs::read(&os_backup_path).await.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
os_backup_path.display().to_string(),
|
||||
)
|
||||
})?)?;
|
||||
let mut os_backup: OsBackup = IoFormat::Cbor.from_slice(
|
||||
&tokio::fs::read(&os_backup_path)
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, os_backup_path.display().to_string()))?,
|
||||
)?;
|
||||
|
||||
os_backup.account.password = argon2::hash_encoded(
|
||||
embassy_password.as_bytes(),
|
||||
&rand::random::<[u8; 16]>()[..],
|
||||
&argon2::Config::default(),
|
||||
)
|
||||
.with_kind(crate::ErrorKind::PasswordHashGeneration)?;
|
||||
.with_kind(ErrorKind::PasswordHashGeneration)?;
|
||||
|
||||
let secret_store = ctx.secret_store().await?;
|
||||
|
||||
@@ -211,27 +205,24 @@ pub async fn recover_full_embassy(
|
||||
|
||||
let rpc_ctx = RpcContext::init(ctx.config_path.clone(), disk_guid.clone()).await?;
|
||||
|
||||
let mut db = rpc_ctx.db.handle();
|
||||
|
||||
let ids = backup_guard
|
||||
let ids: Vec<_> = backup_guard
|
||||
.metadata
|
||||
.package_backups
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect();
|
||||
let (backup_guard, tasks, progress_info) =
|
||||
restore_packages(&rpc_ctx, &mut db, backup_guard, ids).await?;
|
||||
restore_packages(&rpc_ctx, backup_guard, ids).await?;
|
||||
let task_consumer_rpc_ctx = rpc_ctx.clone();
|
||||
tokio::select! {
|
||||
_ = async move {
|
||||
stream::iter(tasks.into_iter().map(|x| (x, task_consumer_rpc_ctx.clone())))
|
||||
.for_each_concurrent(5, |(res, ctx)| async move {
|
||||
let mut db = ctx.db.handle();
|
||||
match res.await {
|
||||
(Ok(_), _) => (),
|
||||
(Err(err), package_id) => {
|
||||
if let Err(err) = ctx.notification_manager.notify(
|
||||
&mut db,
|
||||
ctx.db.clone(),
|
||||
Some(package_id.clone()),
|
||||
NotificationLevel::Error,
|
||||
"Restoration Failure".to_string(), format!("Error restoring package {}: {}", package_id,err), (), None).await{
|
||||
@@ -261,9 +252,9 @@ pub async fn recover_full_embassy(
|
||||
))
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, backup_guard))]
|
||||
async fn restore_packages(
|
||||
ctx: &RpcContext,
|
||||
db: &mut PatchDbHandle,
|
||||
backup_guard: BackupMountGuard<TmpMountGuard>,
|
||||
ids: Vec<PackageId>,
|
||||
) -> Result<
|
||||
@@ -274,7 +265,7 @@ async fn restore_packages(
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let guards = assure_restoring(ctx, db, ids, &backup_guard).await?;
|
||||
let guards = assure_restoring(ctx, ids, &backup_guard).await?;
|
||||
|
||||
let mut progress_info = ProgressInfo::default();
|
||||
|
||||
@@ -282,7 +273,9 @@ async fn restore_packages(
|
||||
for (manifest, guard) in guards {
|
||||
let id = manifest.id.clone();
|
||||
let (progress, task) = restore_package(ctx.clone(), manifest, guard).await?;
|
||||
progress_info.package_installs.insert(id.clone(), progress);
|
||||
progress_info
|
||||
.package_installs
|
||||
.insert(id.clone(), progress.clone());
|
||||
progress_info
|
||||
.src_volume_size
|
||||
.insert(id.clone(), dir_size(backup_dir(&id), None).await?);
|
||||
@@ -306,23 +299,20 @@ async fn restore_packages(
|
||||
Ok((backup_guard, tasks, progress_info))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
#[instrument(skip(ctx, backup_guard))]
|
||||
async fn assure_restoring(
|
||||
ctx: &RpcContext,
|
||||
db: &mut PatchDbHandle,
|
||||
ids: Vec<PackageId>,
|
||||
backup_guard: &BackupMountGuard<TmpMountGuard>,
|
||||
) -> Result<Vec<(Manifest, PackageBackupMountGuard)>, Error> {
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let mut guards = Vec::with_capacity(ids.len());
|
||||
|
||||
let mut insert_packages = BTreeMap::new();
|
||||
|
||||
for id in ids {
|
||||
let mut model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&id)
|
||||
.get_mut(&mut tx)
|
||||
.await?;
|
||||
let peek = ctx.db.peek().await?;
|
||||
|
||||
let model = peek.as_package_data().as_idx(&id);
|
||||
|
||||
if !model.is_none() {
|
||||
return Err(Error::new(
|
||||
@@ -330,14 +320,15 @@ async fn assure_restoring(
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
|
||||
let guard = backup_guard.mount_package_backup(&id).await?;
|
||||
let s9pk_path = Path::new(BACKUP_DIR).join(&id).join(format!("{}.s9pk", id));
|
||||
let mut rdr = S9pkReader::open(&s9pk_path, false).await?;
|
||||
|
||||
let manifest = rdr.manifest().await?;
|
||||
let version = manifest.version.clone();
|
||||
let progress = InstallProgress::new(Some(tokio::fs::metadata(&s9pk_path).await?.len()));
|
||||
let progress = Arc::new(InstallProgress::new(Some(
|
||||
tokio::fs::metadata(&s9pk_path).await?.len(),
|
||||
)));
|
||||
|
||||
let public_dir_path = ctx
|
||||
.datadir
|
||||
@@ -361,22 +352,29 @@ async fn assure_restoring(
|
||||
let mut dst = File::create(&icon_path).await?;
|
||||
tokio::io::copy(&mut rdr.icon().await?, &mut dst).await?;
|
||||
dst.sync_all().await?;
|
||||
|
||||
*model = Some(PackageDataEntry::Restoring {
|
||||
install_progress: progress.clone(),
|
||||
static_files: StaticFiles::local(&id, &version, manifest.assets.icon_type()),
|
||||
manifest: manifest.clone(),
|
||||
});
|
||||
model.save(&mut tx).await?;
|
||||
insert_packages.insert(
|
||||
id.clone(),
|
||||
PackageDataEntry::Restoring(PackageDataEntryRestoring {
|
||||
install_progress: progress.clone(),
|
||||
static_files: StaticFiles::local(&id, &version, manifest.assets.icon_type()),
|
||||
manifest: manifest.clone(),
|
||||
}),
|
||||
);
|
||||
|
||||
guards.push((manifest, guard));
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
for (id, package) in insert_packages {
|
||||
db.as_package_data_mut().insert(&id, &package)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
Ok(guards)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
#[instrument(skip(ctx, guard))]
|
||||
async fn restore_package<'a>(
|
||||
ctx: RpcContext,
|
||||
manifest: Manifest,
|
||||
@@ -388,13 +386,11 @@ async fn restore_package<'a>(
|
||||
.join(format!("{}.s9pk", id));
|
||||
|
||||
let metadata_path = Path::new(BACKUP_DIR).join(&id).join("metadata.cbor");
|
||||
let metadata: BackupMetadata =
|
||||
IoFormat::Cbor.from_slice(&tokio::fs::read(&metadata_path).await.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
metadata_path.display().to_string(),
|
||||
)
|
||||
})?)?;
|
||||
let metadata: BackupMetadata = IoFormat::Cbor.from_slice(
|
||||
&tokio::fs::read(&metadata_path)
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, metadata_path.display().to_string()))?,
|
||||
)?;
|
||||
|
||||
let mut secrets = ctx.secret_store.acquire().await?;
|
||||
let mut secrets_tx = secrets.begin().await?;
|
||||
@@ -402,8 +398,8 @@ async fn restore_package<'a>(
|
||||
let k = key.0.as_slice();
|
||||
sqlx::query!(
|
||||
"INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING",
|
||||
*id,
|
||||
*iface,
|
||||
id.to_string(),
|
||||
iface.to_string(),
|
||||
k,
|
||||
)
|
||||
.execute(&mut secrets_tx).await?;
|
||||
@@ -413,8 +409,8 @@ async fn restore_package<'a>(
|
||||
let k = key.0.as_slice();
|
||||
sqlx::query!(
|
||||
"INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING",
|
||||
*id,
|
||||
*iface,
|
||||
id.to_string(),
|
||||
iface.to_string(),
|
||||
k,
|
||||
)
|
||||
.execute(&mut secrets_tx).await?;
|
||||
@@ -424,26 +420,37 @@ async fn restore_package<'a>(
|
||||
|
||||
let len = tokio::fs::metadata(&s9pk_path)
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
s9pk_path.display().to_string(),
|
||||
)
|
||||
})?
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, s9pk_path.display().to_string()))?
|
||||
.len();
|
||||
let file = File::open(&s9pk_path).await.with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Filesystem,
|
||||
s9pk_path.display().to_string(),
|
||||
)
|
||||
})?;
|
||||
let file = File::open(&s9pk_path)
|
||||
.await
|
||||
.with_ctx(|_| (ErrorKind::Filesystem, s9pk_path.display().to_string()))?;
|
||||
|
||||
let progress = InstallProgress::new(Some(len));
|
||||
let marketplace_url = metadata.marketplace_url;
|
||||
|
||||
let progress = Arc::new(progress);
|
||||
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_package_data_mut().insert(
|
||||
&id,
|
||||
&PackageDataEntry::Restoring(PackageDataEntryRestoring {
|
||||
install_progress: progress.clone(),
|
||||
static_files: StaticFiles::local(
|
||||
&id,
|
||||
&manifest.version,
|
||||
manifest.assets.icon_type(),
|
||||
),
|
||||
manifest: manifest.clone(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
Ok((
|
||||
progress.clone(),
|
||||
async move {
|
||||
download_install_s9pk(&ctx, &manifest, None, progress, file, None).await?;
|
||||
download_install_s9pk(ctx, manifest, marketplace_url, progress, file, None).await?;
|
||||
|
||||
guard.unmount().await?;
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ use crate::disk::mount::filesystem::cifs::Cifs;
|
||||
use crate::disk::mount::filesystem::ReadOnly;
|
||||
use crate::disk::mount::guard::TmpMountGuard;
|
||||
use crate::disk::util::{recovery_info, EmbassyOsRecoveryInfo};
|
||||
use crate::prelude::*;
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::KeyVal;
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
@@ -84,7 +84,7 @@ pub async fn update(
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
crate::ErrorKind::NotFound,
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
let cifs = Cifs {
|
||||
@@ -112,7 +112,7 @@ pub async fn update(
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", BackupTargetId::Cifs { id }),
|
||||
crate::ErrorKind::NotFound,
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
Ok(KeyVal {
|
||||
@@ -134,7 +134,7 @@ pub async fn remove(#[context] ctx: RpcContext, #[arg] id: BackupTargetId) -> Re
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", id),
|
||||
crate::ErrorKind::NotFound,
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
if sqlx::query!("DELETE FROM cifs_shares WHERE id = $1", id)
|
||||
@@ -145,7 +145,7 @@ pub async fn remove(#[context] ctx: RpcContext, #[arg] id: BackupTargetId) -> Re
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("Backup Target ID {} Not Found", BackupTargetId::Cifs { id }),
|
||||
crate::ErrorKind::NotFound,
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
};
|
||||
Ok(())
|
||||
|
||||
@@ -7,7 +7,6 @@ use clap::ArgMatches;
|
||||
use color_eyre::eyre::eyre;
|
||||
use digest::generic_array::GenericArray;
|
||||
use digest::OutputSizeUser;
|
||||
use lazy_static::lazy_static;
|
||||
use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
@@ -23,10 +22,10 @@ use crate::disk::mount::filesystem::cifs::Cifs;
|
||||
use crate::disk::mount::filesystem::{FileSystem, MountType, ReadWrite};
|
||||
use crate::disk::mount::guard::TmpMountGuard;
|
||||
use crate::disk::util::PartitionInfo;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::{deserialize_from_str, display_serializable, serialize_display};
|
||||
use crate::util::{display_none, Version};
|
||||
use crate::Error;
|
||||
|
||||
pub mod cifs;
|
||||
|
||||
@@ -44,7 +43,7 @@ pub enum BackupTarget {
|
||||
Cifs(CifsBackupTarget),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||
pub enum BackupTargetId {
|
||||
Disk { logicalname: PathBuf },
|
||||
Cifs { id: i32 },
|
||||
@@ -73,14 +72,14 @@ impl std::fmt::Display for BackupTargetId {
|
||||
impl std::str::FromStr for BackupTargetId {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.split_once("-") {
|
||||
match s.split_once('-') {
|
||||
Some(("disk", logicalname)) => Ok(BackupTargetId::Disk {
|
||||
logicalname: Path::new(logicalname).to_owned(),
|
||||
}),
|
||||
Some(("cifs", id)) => Ok(BackupTargetId::Cifs { id: id.parse()? }),
|
||||
_ => Err(Error::new(
|
||||
eyre!("Invalid Backup Target ID"),
|
||||
crate::ErrorKind::InvalidBackupTargetId,
|
||||
ErrorKind::InvalidBackupTargetId,
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -214,7 +213,7 @@ fn display_backup_info(info: BackupInfo, matches: &ArgMatches) {
|
||||
]);
|
||||
for (id, info) in info.package_backups {
|
||||
let row = row![
|
||||
id.as_str(),
|
||||
&*id,
|
||||
info.version.as_str(),
|
||||
info.os_version.as_str(),
|
||||
&info.timestamp.to_string(),
|
||||
@@ -225,7 +224,7 @@ fn display_backup_info(info: BackupInfo, matches: &ArgMatches) {
|
||||
}
|
||||
|
||||
#[command(display(display_backup_info))]
|
||||
#[instrument(skip_all)]
|
||||
#[instrument(skip(ctx, password))]
|
||||
pub async fn info(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg(rename = "target-id")] target_id: BackupTargetId,
|
||||
@@ -250,7 +249,7 @@ pub async fn info(
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
lazy_static::lazy_static! {
|
||||
static ref USER_MOUNTS: Mutex<BTreeMap<BackupTargetId, BackupMountGuard<TmpMountGuard>>> =
|
||||
Mutex::new(BTreeMap::new());
|
||||
}
|
||||
@@ -287,11 +286,10 @@ pub async fn mount(
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[command(display(display_none))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn umount(
|
||||
#[context] ctx: RpcContext,
|
||||
#[context] _ctx: RpcContext,
|
||||
#[arg(rename = "target-id")] target_id: Option<BackupTargetId>,
|
||||
) -> Result<(), Error> {
|
||||
let mut mounts = USER_MOUNTS.lock().await;
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use models::ImageId;
|
||||
use nix::sys::signal::Signal;
|
||||
use patch_db::HasModel;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
@@ -10,6 +9,7 @@ use tracing::instrument;
|
||||
use super::{Config, ConfigSpec};
|
||||
use crate::context::RpcContext;
|
||||
use crate::dependencies::Dependencies;
|
||||
use crate::prelude::*;
|
||||
use crate::procedure::docker::DockerContainers;
|
||||
use crate::procedure::{PackageProcedure, ProcedureName};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
@@ -18,7 +18,7 @@ use crate::util::Version;
|
||||
use crate::volume::Volumes;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct ConfigRes {
|
||||
pub config: Option<Config>,
|
||||
@@ -26,6 +26,7 @@ pub struct ConfigRes {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct ConfigActions {
|
||||
pub get: PackageProcedure,
|
||||
pub set: PackageProcedure,
|
||||
@@ -34,7 +35,7 @@ impl ConfigActions {
|
||||
#[instrument(skip_all)]
|
||||
pub fn validate(
|
||||
&self,
|
||||
container: &Option<DockerContainers>,
|
||||
_container: &Option<DockerContainers>,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use indexmap::IndexSet;
|
||||
use itertools::Itertools;
|
||||
use models::ErrorKind;
|
||||
use patch_db::{DbHandle, LockReceipt, LockTarget, LockTargetId, LockType, Verifier};
|
||||
use models::{ErrorKind, OptionExt};
|
||||
use patch_db::value::InternedString;
|
||||
use patch_db::Value;
|
||||
use regex::Regex;
|
||||
use rpc_toolkit::command;
|
||||
use serde_json::Value;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::{CurrentDependencies, CurrentDependents};
|
||||
use crate::dependencies::{
|
||||
BreakTransitiveReceipts, BreakageRes, Dependencies, DependencyConfig, DependencyErrors,
|
||||
DependencyReceipt, TaggedDependencyError, TryHealReceipts,
|
||||
};
|
||||
use crate::install::cleanup::UpdateDependencyReceipts;
|
||||
use crate::procedure::docker::DockerContainers;
|
||||
use crate::db::model::CurrentDependencies;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat};
|
||||
@@ -32,10 +28,10 @@ pub mod util;
|
||||
pub use spec::{ConfigSpec, Defaultable};
|
||||
use util::NumRange;
|
||||
|
||||
use self::action::{ConfigActions, ConfigRes};
|
||||
use self::spec::{ConfigPointerReceipts, ValueSpecPointer};
|
||||
use self::action::ConfigRes;
|
||||
use self::spec::ValueSpecPointer;
|
||||
|
||||
pub type Config = serde_json::Map<String, Value>;
|
||||
pub type Config = patch_db::value::InOMap<InternedString, Value>;
|
||||
pub trait TypeOf {
|
||||
fn type_of(&self) -> &'static str;
|
||||
}
|
||||
@@ -79,7 +75,7 @@ pub struct TimeoutError;
|
||||
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
pub struct NoMatchWithPath {
|
||||
pub path: Vec<String>,
|
||||
pub path: Vec<InternedString>,
|
||||
pub error: MatchError,
|
||||
}
|
||||
impl NoMatchWithPath {
|
||||
@@ -89,7 +85,7 @@ impl NoMatchWithPath {
|
||||
error,
|
||||
}
|
||||
}
|
||||
pub fn prepend(mut self, seg: String) -> Self {
|
||||
pub fn prepend(mut self, seg: InternedString) -> Self {
|
||||
self.path.push(seg);
|
||||
self
|
||||
}
|
||||
@@ -108,9 +104,9 @@ impl From<NoMatchWithPath> for Error {
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
pub enum MatchError {
|
||||
#[error("String {0:?} Does Not Match Pattern {1}")]
|
||||
Pattern(String, Regex),
|
||||
Pattern(Arc<String>, Regex),
|
||||
#[error("String {0:?} Is Not In Enum {1:?}")]
|
||||
Enum(String, IndexSet<String>),
|
||||
Enum(Arc<String>, IndexSet<String>),
|
||||
#[error("Field Is Not Nullable")]
|
||||
NotNullable,
|
||||
#[error("Length Mismatch: expected {0}, actual: {1}")]
|
||||
@@ -122,11 +118,11 @@ pub enum MatchError {
|
||||
#[error("Number Is Not Integral: {0}")]
|
||||
NonIntegral(f64),
|
||||
#[error("Variant {0:?} Is Not In Union {1:?}")]
|
||||
Union(String, IndexSet<String>),
|
||||
Union(Arc<String>, IndexSet<String>),
|
||||
#[error("Variant Is Missing Tag {0:?}")]
|
||||
MissingTag(String),
|
||||
MissingTag(InternedString),
|
||||
#[error("Property {0:?} Of Variant {1:?} Conflicts With Union Tag")]
|
||||
PropertyMatchesUnionTag(String, String),
|
||||
PropertyMatchesUnionTag(InternedString, String),
|
||||
#[error("Name of Property {0:?} Conflicts With Map Tag Name")]
|
||||
PropertyNameMatchesMapTag(String),
|
||||
#[error("Pointer Is Invalid: {0}")]
|
||||
@@ -162,55 +158,6 @@ pub fn config(#[arg] id: PackageId) -> Result<PackageId, Error> {
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub struct ConfigGetReceipts {
|
||||
manifest_volumes: LockReceipt<crate::volume::Volumes, ()>,
|
||||
manifest_version: LockReceipt<crate::util::Version, ()>,
|
||||
manifest_config: LockReceipt<Option<ConfigActions>, ()>,
|
||||
}
|
||||
|
||||
impl ConfigGetReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks, id);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<LockTargetId>,
|
||||
id: &PackageId,
|
||||
) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||
let manifest_version = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.manifest().version())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let manifest_volumes = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.manifest().volumes())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let manifest_config = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.manifest().config())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
manifest_volumes: manifest_volumes.verify(skeleton_key)?,
|
||||
manifest_version: manifest_version.verify(skeleton_key)?,
|
||||
manifest_config: manifest_config.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[command(display(display_serializable))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get(
|
||||
@@ -220,16 +167,21 @@ pub async fn get(
|
||||
#[arg(long = "format")]
|
||||
format: Option<IoFormat>,
|
||||
) -> Result<ConfigRes, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let receipts = ConfigGetReceipts::new(&mut db, &id).await?;
|
||||
let action = receipts
|
||||
.manifest_config
|
||||
.get(&mut db)
|
||||
.await?
|
||||
let db = ctx.db.peek().await?;
|
||||
let manifest = db
|
||||
.as_package_data()
|
||||
.as_idx(&id)
|
||||
.or_not_found(&id)?
|
||||
.as_installed()
|
||||
.or_not_found(&id)?
|
||||
.as_manifest();
|
||||
let action = manifest
|
||||
.as_config()
|
||||
.de()?
|
||||
.ok_or_else(|| Error::new(eyre!("{} has no config", id), crate::ErrorKind::NotFound))?;
|
||||
|
||||
let volumes = receipts.manifest_volumes.get(&mut db).await?;
|
||||
let version = receipts.manifest_version.get(&mut db).await?;
|
||||
let volumes = manifest.as_volumes().de()?;
|
||||
let version = manifest.as_version().de()?;
|
||||
action.get(&ctx, &id, &version, &volumes).await
|
||||
}
|
||||
|
||||
@@ -250,172 +202,12 @@ pub fn set(
|
||||
Ok((id, config, timeout.map(|d| *d)))
|
||||
}
|
||||
|
||||
/// So, the new locking finds all the possible locks and lifts them up into a bundle of locks.
|
||||
/// Then this bundle will be passed down into the functions that will need to touch the db, and
|
||||
/// instead of doing the locks down in the system, we have already done the locks and can
|
||||
/// do the operation on the db.
|
||||
/// An UnlockedLock has two types, the type of setting and getting from the db, and the second type
|
||||
/// is the keys that we need to insert on getting/setting because we have included wild cards into the paths.
|
||||
pub struct ConfigReceipts {
|
||||
pub dependency_receipt: DependencyReceipt,
|
||||
pub config_receipts: ConfigPointerReceipts,
|
||||
pub update_dependency_receipts: UpdateDependencyReceipts,
|
||||
pub try_heal_receipts: TryHealReceipts,
|
||||
pub break_transitive_receipts: BreakTransitiveReceipts,
|
||||
pub configured: LockReceipt<bool, String>,
|
||||
pub config_actions: LockReceipt<ConfigActions, String>,
|
||||
pub dependencies: LockReceipt<Dependencies, String>,
|
||||
pub volumes: LockReceipt<crate::volume::Volumes, String>,
|
||||
pub version: LockReceipt<crate::util::Version, String>,
|
||||
pub manifest: LockReceipt<Manifest, String>,
|
||||
pub system_pointers: LockReceipt<Vec<spec::SystemPointerSpec>, String>,
|
||||
pub current_dependents: LockReceipt<CurrentDependents, String>,
|
||||
pub current_dependencies: LockReceipt<CurrentDependencies, String>,
|
||||
pub dependency_errors: LockReceipt<DependencyErrors, String>,
|
||||
pub manifest_dependencies_config: LockReceipt<DependencyConfig, (String, String)>,
|
||||
pub docker_containers: LockReceipt<DockerContainers, String>,
|
||||
}
|
||||
|
||||
impl ConfigReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||
let dependency_receipt = DependencyReceipt::setup(locks);
|
||||
let config_receipts = ConfigPointerReceipts::setup(locks);
|
||||
let update_dependency_receipts = UpdateDependencyReceipts::setup(locks);
|
||||
let break_transitive_receipts = BreakTransitiveReceipts::setup(locks);
|
||||
let try_heal_receipts = TryHealReceipts::setup(locks);
|
||||
|
||||
let configured: LockTarget<bool, String> = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.status().configured())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let config_actions = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.and_then(|x| x.manifest().config())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let dependencies = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest().dependencies())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let volumes = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest().volumes())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let version = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest().version())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let manifest = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let system_pointers = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.system_pointers())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let current_dependents = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.current_dependents())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let current_dependencies = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.current_dependencies())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let dependency_errors = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.status().dependency_errors())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let manifest_dependencies_config = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.and_then(|x| x.manifest().dependencies().star().config())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let docker_containers = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.and_then(|x| x.manifest().containers())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
dependency_receipt: dependency_receipt(skeleton_key)?,
|
||||
config_receipts: config_receipts(skeleton_key)?,
|
||||
try_heal_receipts: try_heal_receipts(skeleton_key)?,
|
||||
break_transitive_receipts: break_transitive_receipts(skeleton_key)?,
|
||||
update_dependency_receipts: update_dependency_receipts(skeleton_key)?,
|
||||
configured: configured.verify(skeleton_key)?,
|
||||
config_actions: config_actions.verify(skeleton_key)?,
|
||||
dependencies: dependencies.verify(skeleton_key)?,
|
||||
volumes: volumes.verify(skeleton_key)?,
|
||||
version: version.verify(skeleton_key)?,
|
||||
manifest: manifest.verify(skeleton_key)?,
|
||||
system_pointers: system_pointers.verify(skeleton_key)?,
|
||||
current_dependents: current_dependents.verify(skeleton_key)?,
|
||||
current_dependencies: current_dependencies.verify(skeleton_key)?,
|
||||
dependency_errors: dependency_errors.verify(skeleton_key)?,
|
||||
manifest_dependencies_config: manifest_dependencies_config.verify(skeleton_key)?,
|
||||
docker_containers: docker_containers.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[command(rename = "dry", display(display_serializable))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn set_dry(
|
||||
#[context] ctx: RpcContext,
|
||||
#[parent_data] (id, config, timeout): (PackageId, Option<Config>, Option<Duration>),
|
||||
) -> Result<BreakageRes, Error> {
|
||||
) -> Result<BTreeMap<PackageId, String>, Error> {
|
||||
let breakages = BTreeMap::new();
|
||||
let overrides = Default::default();
|
||||
|
||||
@@ -428,11 +220,11 @@ pub async fn set_dry(
|
||||
};
|
||||
let breakages = configure(&ctx, &id, configure_context).await?;
|
||||
|
||||
Ok(BreakageRes(breakages))
|
||||
Ok(breakages)
|
||||
}
|
||||
|
||||
pub struct ConfigureContext {
|
||||
pub breakages: BTreeMap<PackageId, TaggedDependencyError>,
|
||||
pub breakages: BTreeMap<PackageId, String>,
|
||||
pub timeout: Option<Duration>,
|
||||
pub config: Option<Config>,
|
||||
pub overrides: BTreeMap<PackageId, Config>,
|
||||
@@ -463,20 +255,15 @@ pub async fn configure(
|
||||
ctx: &RpcContext,
|
||||
id: &PackageId,
|
||||
configure_context: ConfigureContext,
|
||||
) -> Result<BTreeMap<PackageId, TaggedDependencyError>, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let version = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.expect(&mut db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(&mut db)
|
||||
.await?
|
||||
.manifest()
|
||||
.version()
|
||||
.get(&mut ctx.db.handle())
|
||||
.await?;
|
||||
) -> Result<BTreeMap<PackageId, String>, Error> {
|
||||
let db = ctx.db.peek().await?;
|
||||
let package = db
|
||||
.as_package_data()
|
||||
.as_idx(id)
|
||||
.or_not_found(&id)?
|
||||
.as_installed()
|
||||
.or_not_found(&id)?;
|
||||
let version = package.as_manifest().as_version().de()?;
|
||||
ctx.managers
|
||||
.get(&(id.clone(), version.clone()))
|
||||
.await
|
||||
@@ -499,63 +286,3 @@ macro_rules! not_found {
|
||||
};
|
||||
}
|
||||
pub(crate) use not_found;
|
||||
|
||||
/// We want to have a double check that the paths are what we expect them to be.
|
||||
/// Found that earlier the paths where not what we expected them to be.
|
||||
#[tokio::test]
|
||||
async fn ensure_creation_of_config_paths_makes_sense() {
|
||||
let mut fake = patch_db::test_utils::NoOpDb();
|
||||
let config_locks = ConfigReceipts::new(&mut fake).await.unwrap();
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.configured.lock.glob),
|
||||
"/package-data/*/installed/status/configured"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.config_actions.lock.glob),
|
||||
"/package-data/*/installed/manifest/config"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.dependencies.lock.glob),
|
||||
"/package-data/*/installed/manifest/dependencies"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.volumes.lock.glob),
|
||||
"/package-data/*/installed/manifest/volumes"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.version.lock.glob),
|
||||
"/package-data/*/installed/manifest/version"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.volumes.lock.glob),
|
||||
"/package-data/*/installed/manifest/volumes"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.manifest.lock.glob),
|
||||
"/package-data/*/installed/manifest"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.manifest.lock.glob),
|
||||
"/package-data/*/installed/manifest"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.system_pointers.lock.glob),
|
||||
"/package-data/*/installed/system-pointers"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.current_dependents.lock.glob),
|
||||
"/package-data/*/installed/current-dependents"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.dependency_errors.lock.glob),
|
||||
"/package-data/*/installed/status/dependency-errors"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.manifest_dependencies_config.lock.glob),
|
||||
"/package-data/*/installed/manifest/dependencies/*/config"
|
||||
);
|
||||
assert_eq!(
|
||||
&format!("{}", config_locks.system_pointers.lock.glob),
|
||||
"/package-data/*/installed/system-pointers"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,15 +9,16 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use imbl::Vector;
|
||||
use imbl_value::InternedString;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use itertools::Itertools;
|
||||
use jsonpath_lib::Compiled as CompiledJsonPath;
|
||||
use patch_db::{DbHandle, LockReceipt, LockType};
|
||||
use patch_db::value::{Number, Value};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use regex::Regex;
|
||||
use serde::de::{MapAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::{Number, Value};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::util::{self, CharSet, NumRange, UniqueBy, STATIC_NULL};
|
||||
@@ -26,8 +27,8 @@ use crate::config::ConfigurationError;
|
||||
use crate::context::RpcContext;
|
||||
use crate::net::interface::InterfaceId;
|
||||
use crate::net::keys::Key;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::Error;
|
||||
|
||||
// Config Value Specifications
|
||||
#[async_trait]
|
||||
@@ -39,14 +40,12 @@ pub trait ValueSpec {
|
||||
// since not all inVariant can be checked by the type
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath>;
|
||||
// update is to fill in values for environment pointers recursively
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError>;
|
||||
// returns all pointers that are live in the provided config
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath>;
|
||||
@@ -156,17 +155,15 @@ where
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
self.inner.validate(manifest)
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
self.inner
|
||||
.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.update(ctx, manifest, config_overrides, value)
|
||||
.await
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -201,17 +198,15 @@ where
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
self.inner.validate(manifest)
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
self.inner
|
||||
.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.update(ctx, manifest, config_overrides, value)
|
||||
.await
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -279,17 +274,15 @@ where
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
self.inner.validate(manifest)
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
self.inner
|
||||
.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.update(ctx, manifest, config_overrides, value)
|
||||
.await
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -394,48 +387,22 @@ impl ValueSpec for ValueSpecAny {
|
||||
ValueSpecAny::Pointer(a) => a.validate(manifest),
|
||||
}
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
match self {
|
||||
ValueSpecAny::Boolean(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Enum(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::List(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Number(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Object(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::String(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Union(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Pointer(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecAny::Boolean(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::Enum(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::List(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::Number(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::Object(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::String(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::Union(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecAny::Pointer(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
}
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -513,14 +480,12 @@ impl ValueSpec for ValueSpecBoolean {
|
||||
fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
_ctx: &RpcContext,
|
||||
_db: &mut Db,
|
||||
_manifest: &Manifest,
|
||||
_config_overrides: &BTreeMap<PackageId, Config>,
|
||||
_value: &mut Value,
|
||||
_receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -584,7 +549,7 @@ impl ValueSpec for ValueSpecEnum {
|
||||
fn matches(&self, val: &Value) -> Result<(), NoMatchWithPath> {
|
||||
match val {
|
||||
Value::String(b) => {
|
||||
if self.values.contains(b) {
|
||||
if self.values.contains(&**b) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(NoMatchWithPath::new(MatchError::Enum(
|
||||
@@ -603,14 +568,12 @@ impl ValueSpec for ValueSpecEnum {
|
||||
fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
_ctx: &RpcContext,
|
||||
_db: &mut Db,
|
||||
_manifest: &Manifest,
|
||||
_config_overrides: &BTreeMap<PackageId, Config>,
|
||||
_value: &mut Value,
|
||||
_receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -628,7 +591,7 @@ impl ValueSpec for ValueSpecEnum {
|
||||
}
|
||||
}
|
||||
impl DefaultableWith for ValueSpecEnum {
|
||||
type DefaultSpec = String;
|
||||
type DefaultSpec = Arc<String>;
|
||||
type Error = crate::util::Never;
|
||||
|
||||
fn gen_with<R: Rng + CryptoRng + Sync + Send + Send>(
|
||||
@@ -666,13 +629,13 @@ where
|
||||
.map(|(i, v)| {
|
||||
self.spec
|
||||
.matches(v)
|
||||
.map_err(|e| e.prepend(format!("{}", i)))?;
|
||||
.map_err(|e| e.prepend(InternedString::from_display(&i)))?;
|
||||
if l.iter()
|
||||
.enumerate()
|
||||
.any(|(i2, v2)| i != i2 && self.spec.eq(v, v2))
|
||||
{
|
||||
Err(NoMatchWithPath::new(MatchError::ListUniquenessViolation)
|
||||
.prepend(format!("{}", i)))
|
||||
.prepend(InternedString::from_display(&i)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -690,25 +653,19 @@ where
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
self.spec.validate(manifest)
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
if let Value::Array(ref mut ls) = value {
|
||||
for (i, val) in ls.into_iter().enumerate() {
|
||||
match self
|
||||
.spec
|
||||
.update(ctx, db, manifest, config_overrides, val, receipts)
|
||||
.await
|
||||
{
|
||||
Err(ConfigurationError::NoMatch(e)) => {
|
||||
Err(ConfigurationError::NoMatch(e.prepend(format!("{}", i))))
|
||||
}
|
||||
for (i, val) in ls.iter_mut().enumerate() {
|
||||
match self.spec.update(ctx, manifest, config_overrides, val).await {
|
||||
Err(ConfigurationError::NoMatch(e)) => Err(ConfigurationError::NoMatch(
|
||||
e.prepend(InternedString::from_display(&i)),
|
||||
)),
|
||||
a => a,
|
||||
}?;
|
||||
}
|
||||
@@ -755,9 +712,9 @@ where
|
||||
rng: &mut R,
|
||||
timeout: &Option<Duration>,
|
||||
) -> Result<Value, Self::Error> {
|
||||
let mut res = Vec::new();
|
||||
let mut res = Vector::new();
|
||||
for spec_member in spec.iter() {
|
||||
res.push(self.spec.gen_with(spec_member, rng, timeout)?);
|
||||
res.push_back(self.spec.gen_with(spec_member, rng, timeout)?);
|
||||
}
|
||||
Ok(Value::Array(res))
|
||||
}
|
||||
@@ -798,36 +755,19 @@ impl ValueSpec for ValueSpecList {
|
||||
ValueSpecList::Union(a) => a.validate(manifest),
|
||||
}
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
match self {
|
||||
ValueSpecList::Enum(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecList::Number(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecList::Object(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecList::String(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecList::Union(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecList::Enum(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecList::Number(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecList::Object(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecList::String(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecList::Union(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
}
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -885,7 +825,7 @@ impl Defaultable for ValueSpecList {
|
||||
)
|
||||
.contains(&ret.len())
|
||||
{
|
||||
ret.push(
|
||||
ret.push_back(
|
||||
a.inner
|
||||
.inner
|
||||
.spec
|
||||
@@ -941,14 +881,12 @@ impl ValueSpec for ValueSpecNumber {
|
||||
fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
_ctx: &RpcContext,
|
||||
_db: &mut Db,
|
||||
_manifest: &Manifest,
|
||||
_config_overrides: &BTreeMap<PackageId, Config>,
|
||||
_value: &mut Value,
|
||||
_receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -1005,19 +943,15 @@ impl ValueSpec for ValueSpecObject {
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
self.spec.validate(manifest)
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
if let Value::Object(o) = value {
|
||||
self.spec
|
||||
.update(ctx, db, manifest, config_overrides, o, receipts)
|
||||
.await
|
||||
self.spec.update(ctx, manifest, config_overrides, o).await
|
||||
} else {
|
||||
Err(ConfigurationError::NoMatch(NoMatchWithPath::new(
|
||||
MatchError::InvalidType("object", value.type_of()),
|
||||
@@ -1074,11 +1008,11 @@ impl Defaultable for ValueSpecObject {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ConfigSpec(pub IndexMap<String, ValueSpecAny>);
|
||||
pub struct ConfigSpec(pub IndexMap<InternedString, ValueSpecAny>);
|
||||
impl ConfigSpec {
|
||||
pub fn matches(&self, value: &Config) -> Result<(), NoMatchWithPath> {
|
||||
for (key, val) in self.0.iter() {
|
||||
if let Some(v) = value.get(key) {
|
||||
if let Some(v) = value.get(&**key) {
|
||||
val.matches(v).map_err(|e| e.prepend(key.clone()))?;
|
||||
} else {
|
||||
val.matches(&Value::Null)
|
||||
@@ -1108,27 +1042,21 @@ impl ConfigSpec {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update<Db: DbHandle>(
|
||||
pub async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
cfg: &mut Config,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
for (k, vs) in self.0.iter() {
|
||||
match cfg.get_mut(k) {
|
||||
None => {
|
||||
let mut v = Value::Null;
|
||||
vs.update(ctx, db, manifest, config_overrides, &mut v, receipts)
|
||||
.await?;
|
||||
vs.update(ctx, manifest, config_overrides, &mut v).await?;
|
||||
cfg.insert(k.clone(), v);
|
||||
}
|
||||
Some(v) => match vs
|
||||
.update(ctx, db, manifest, config_overrides, v, receipts)
|
||||
.await
|
||||
{
|
||||
Some(v) => match vs.update(ctx, manifest, config_overrides, v).await {
|
||||
Err(ConfigurationError::NoMatch(e)) => {
|
||||
Err(ConfigurationError::NoMatch(e.prepend(k.clone())))
|
||||
}
|
||||
@@ -1247,7 +1175,7 @@ impl<'de> Deserialize<'de> for ValueSpecString {
|
||||
})
|
||||
}
|
||||
}
|
||||
const FIELDS: &'static [&'static str] = &[
|
||||
const FIELDS: &[&str] = &[
|
||||
"pattern",
|
||||
"pattern-description",
|
||||
"textarea",
|
||||
@@ -1268,7 +1196,7 @@ impl ValueSpec for ValueSpecString {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(NoMatchWithPath::new(MatchError::Pattern(
|
||||
s.to_owned(),
|
||||
s.clone(),
|
||||
pattern.pattern.clone(),
|
||||
)))
|
||||
}
|
||||
@@ -1286,14 +1214,12 @@ impl ValueSpec for ValueSpecString {
|
||||
fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
_ctx: &RpcContext,
|
||||
_db: &mut Db,
|
||||
_manifest: &Manifest,
|
||||
_config_overrides: &BTreeMap<PackageId, Config>,
|
||||
_value: &mut Value,
|
||||
_receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -1352,11 +1278,11 @@ pub enum DefaultString {
|
||||
Entropy(Entropy),
|
||||
}
|
||||
impl DefaultString {
|
||||
pub fn gen<R: Rng + CryptoRng + Sync + Send>(&self, rng: &mut R) -> String {
|
||||
match self {
|
||||
pub fn gen<R: Rng + CryptoRng + Sync + Send>(&self, rng: &mut R) -> Arc<String> {
|
||||
Arc::new(match self {
|
||||
DefaultString::Literal(s) => s.clone(),
|
||||
DefaultString::Entropy(e) => e.gen(rng),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1380,7 +1306,7 @@ impl Entropy {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct UnionTag {
|
||||
pub id: String,
|
||||
pub id: InternedString,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub variant_names: BTreeMap<String, String>,
|
||||
@@ -1401,7 +1327,7 @@ impl<'de> serde::de::Deserialize<'de> for ValueSpecUnion {
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(untagged)]
|
||||
pub enum _UnionTag {
|
||||
Old(String),
|
||||
Old(InternedString),
|
||||
New(UnionTag),
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
@@ -1419,7 +1345,7 @@ impl<'de> serde::de::Deserialize<'de> for ValueSpecUnion {
|
||||
tag: match u.tag {
|
||||
_UnionTag::Old(id) => UnionTag {
|
||||
id: id.clone(),
|
||||
name: id,
|
||||
name: id.to_string(),
|
||||
description: None,
|
||||
variant_names: u
|
||||
.variants
|
||||
@@ -1461,10 +1387,10 @@ impl ValueSpec for ValueSpecUnion {
|
||||
fn matches(&self, value: &Value) -> Result<(), NoMatchWithPath> {
|
||||
match value {
|
||||
Value::Object(o) => {
|
||||
if let Some(Value::String(ref tag)) = o.get(&self.tag.id) {
|
||||
if let Some(obj_spec) = self.variants.get(tag) {
|
||||
if let Some(Value::String(ref tag)) = o.get(&*self.tag.id) {
|
||||
if let Some(obj_spec) = self.variants.get(&**tag) {
|
||||
let mut without_tag = o.clone();
|
||||
without_tag.remove(&self.tag.id);
|
||||
without_tag.remove(&*self.tag.id);
|
||||
obj_spec.matches(&without_tag)
|
||||
} else {
|
||||
Err(NoMatchWithPath::new(MatchError::Union(
|
||||
@@ -1487,7 +1413,7 @@ impl ValueSpec for ValueSpecUnion {
|
||||
}
|
||||
fn validate(&self, manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
for (name, variant) in &self.variants {
|
||||
if variant.0.get(&self.tag.id).is_some() {
|
||||
if variant.0.get(&*self.tag.id).is_some() {
|
||||
return Err(NoMatchWithPath::new(MatchError::PropertyMatchesUnionTag(
|
||||
self.tag.id.clone(),
|
||||
name.clone(),
|
||||
@@ -1497,28 +1423,23 @@ impl ValueSpec for ValueSpecUnion {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
if let Value::Object(o) = value {
|
||||
match o.get(&self.tag.id) {
|
||||
match o.get(&*self.tag.id) {
|
||||
None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new(
|
||||
MatchError::MissingTag(self.tag.id.clone()),
|
||||
))),
|
||||
Some(Value::String(tag)) => match self.variants.get(tag) {
|
||||
Some(Value::String(tag)) => match self.variants.get(&**tag) {
|
||||
None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new(
|
||||
MatchError::Union(tag.clone(), self.variants.keys().cloned().collect()),
|
||||
))),
|
||||
Some(spec) => {
|
||||
spec.update(ctx, db, manifest, config_overrides, o, receipts)
|
||||
.await
|
||||
}
|
||||
Some(spec) => spec.update(ctx, manifest, config_overrides, o).await,
|
||||
},
|
||||
Some(other) => Err(ConfigurationError::NoMatch(
|
||||
NoMatchWithPath::new(MatchError::InvalidType("string", other.type_of()))
|
||||
@@ -1533,11 +1454,11 @@ impl ValueSpec for ValueSpecUnion {
|
||||
}
|
||||
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
if let Value::Object(o) = value {
|
||||
match o.get(&self.tag.id) {
|
||||
match o.get(&*self.tag.id) {
|
||||
None => Err(NoMatchWithPath::new(MatchError::MissingTag(
|
||||
self.tag.id.clone(),
|
||||
))),
|
||||
Some(Value::String(tag)) => match self.variants.get(tag) {
|
||||
Some(Value::String(tag)) => match self.variants.get(&**tag) {
|
||||
None => Err(NoMatchWithPath::new(MatchError::Union(
|
||||
tag.clone(),
|
||||
self.variants.keys().cloned().collect(),
|
||||
@@ -1559,8 +1480,8 @@ impl ValueSpec for ValueSpecUnion {
|
||||
}
|
||||
fn requires(&self, id: &PackageId, value: &Value) -> bool {
|
||||
if let Value::Object(o) = value {
|
||||
match o.get(&self.tag.id) {
|
||||
Some(Value::String(tag)) => match self.variants.get(tag) {
|
||||
match o.get(&*self.tag.id) {
|
||||
Some(Value::String(tag)) => match self.variants.get(&**tag) {
|
||||
None => false,
|
||||
Some(spec) => spec.requires(id, o),
|
||||
},
|
||||
@@ -1578,7 +1499,7 @@ impl ValueSpec for ValueSpecUnion {
|
||||
}
|
||||
}
|
||||
impl DefaultableWith for ValueSpecUnion {
|
||||
type DefaultSpec = String;
|
||||
type DefaultSpec = Arc<String>;
|
||||
type Error = ConfigurationError;
|
||||
|
||||
fn gen_with<R: Rng + CryptoRng + Sync + Send>(
|
||||
@@ -1587,7 +1508,7 @@ impl DefaultableWith for ValueSpecUnion {
|
||||
rng: &mut R,
|
||||
timeout: &Option<Duration>,
|
||||
) -> Result<Value, Self::Error> {
|
||||
let variant = if let Some(v) = self.variants.get(spec) {
|
||||
let variant = if let Some(v) = self.variants.get(&**spec) {
|
||||
v
|
||||
} else {
|
||||
return Err(ConfigurationError::NoMatch(NoMatchWithPath::new(
|
||||
@@ -1643,24 +1564,16 @@ impl ValueSpec for ValueSpecPointer {
|
||||
ValueSpecPointer::System(a) => a.validate(manifest),
|
||||
}
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
match self {
|
||||
ValueSpecPointer::Package(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecPointer::System(a) => {
|
||||
a.update(ctx, db, manifest, config_overrides, value, receipts)
|
||||
.await
|
||||
}
|
||||
ValueSpecPointer::Package(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
ValueSpecPointer::System(a) => a.update(ctx, manifest, config_overrides, value).await,
|
||||
}
|
||||
}
|
||||
fn pointers(&self, _value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -1697,23 +1610,17 @@ impl PackagePointerSpec {
|
||||
PackagePointerSpec::Config(ConfigPointer { package_id, .. }) => package_id,
|
||||
}
|
||||
}
|
||||
async fn deref<Db: DbHandle>(
|
||||
async fn deref(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<Value, ConfigurationError> {
|
||||
match &self {
|
||||
PackagePointerSpec::TorKey(key) => key.deref(&manifest.id, &ctx.secret_store).await,
|
||||
PackagePointerSpec::TorAddress(tor) => {
|
||||
tor.deref(db, &receipts.interface_addresses_receipt).await
|
||||
}
|
||||
PackagePointerSpec::LanAddress(lan) => {
|
||||
lan.deref(db, &receipts.interface_addresses_receipt).await
|
||||
}
|
||||
PackagePointerSpec::Config(cfg) => cfg.deref(ctx, db, config_overrides, receipts).await,
|
||||
PackagePointerSpec::TorAddress(tor) => tor.deref(ctx).await,
|
||||
PackagePointerSpec::LanAddress(lan) => lan.deref(ctx).await,
|
||||
PackagePointerSpec::Config(cfg) => cfg.deref(ctx, config_overrides).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1754,18 +1661,14 @@ impl ValueSpec for PackagePointerSpec {
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
*value = self
|
||||
.deref(ctx, db, manifest, config_overrides, receipts)
|
||||
.await?;
|
||||
*value = self.deref(ctx, manifest, config_overrides).await?;
|
||||
Ok(())
|
||||
}
|
||||
fn pointers(&self, _value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
@@ -1788,18 +1691,20 @@ pub struct TorAddressPointer {
|
||||
interface: InterfaceId,
|
||||
}
|
||||
impl TorAddressPointer {
|
||||
async fn deref<Db: DbHandle>(
|
||||
&self,
|
||||
db: &mut Db,
|
||||
receipt: &InterfaceAddressesReceipt,
|
||||
) -> Result<Value, ConfigurationError> {
|
||||
let addr = receipt
|
||||
.interface_addresses
|
||||
.get(db, (&self.package_id, &self.interface))
|
||||
async fn deref(&self, ctx: &RpcContext) -> Result<Value, ConfigurationError> {
|
||||
let addr = ctx
|
||||
.db
|
||||
.peek()
|
||||
.await
|
||||
.map_err(|e| ConfigurationError::SystemError(Error::from(e)))?
|
||||
.and_then(|addresses| addresses.tor_address);
|
||||
Ok(addr.to_owned().map(Value::String).unwrap_or(Value::Null))
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?
|
||||
.as_package_data()
|
||||
.as_idx(&self.package_id)
|
||||
.and_then(|pde| pde.as_installed())
|
||||
.and_then(|i| i.as_interface_addresses().as_idx(&self.interface))
|
||||
.and_then(|a| a.as_tor_address().de().transpose())
|
||||
.transpose()
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?;
|
||||
Ok(addr.map(Arc::new).map(Value::String).unwrap_or(Value::Null))
|
||||
}
|
||||
}
|
||||
impl fmt::Display for TorAddressPointer {
|
||||
@@ -1813,39 +1718,6 @@ impl fmt::Display for TorAddressPointer {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InterfaceAddressesReceipt {
|
||||
interface_addresses: LockReceipt<crate::db::model::InterfaceAddresses, (String, String)>,
|
||||
}
|
||||
|
||||
impl InterfaceAddressesReceipt {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
// let cleanup_receipts = CleanupFailedReceipts::setup(locks);
|
||||
|
||||
let interface_addresses = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.interface_addresses().star())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
// cleanup_receipts: cleanup_receipts(skeleton_key)?,
|
||||
interface_addresses: interface_addresses.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct LanAddressPointer {
|
||||
@@ -1862,73 +1734,27 @@ impl fmt::Display for LanAddressPointer {
|
||||
}
|
||||
}
|
||||
impl LanAddressPointer {
|
||||
async fn deref<Db: DbHandle>(
|
||||
&self,
|
||||
db: &mut Db,
|
||||
receipts: &InterfaceAddressesReceipt,
|
||||
) -> Result<Value, ConfigurationError> {
|
||||
let addr = receipts
|
||||
.interface_addresses
|
||||
.get(db, (&self.package_id, &self.interface))
|
||||
async fn deref(&self, ctx: &RpcContext) -> Result<Value, ConfigurationError> {
|
||||
let addr = ctx
|
||||
.db
|
||||
.peek()
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|x| x.lan_address);
|
||||
Ok(addr.to_owned().map(Value::String).unwrap_or(Value::Null))
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?
|
||||
.as_package_data()
|
||||
.as_idx(&self.package_id)
|
||||
.and_then(|pde| pde.as_installed())
|
||||
.and_then(|i| i.as_interface_addresses().as_idx(&self.interface))
|
||||
.and_then(|a| a.as_lan_address().de().transpose())
|
||||
.transpose()
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?;
|
||||
Ok(addr
|
||||
.to_owned()
|
||||
.map(Arc::new)
|
||||
.map(Value::String)
|
||||
.unwrap_or(Value::Null))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConfigPointerReceipts {
|
||||
interface_addresses_receipt: InterfaceAddressesReceipt,
|
||||
manifest_volumes: LockReceipt<crate::volume::Volumes, String>,
|
||||
manifest_version: LockReceipt<crate::util::Version, String>,
|
||||
config_actions: LockReceipt<super::action::ConfigActions, String>,
|
||||
}
|
||||
|
||||
impl ConfigPointerReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let interface_addresses_receipt = InterfaceAddressesReceipt::setup(locks);
|
||||
|
||||
let manifest_volumes = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest().volumes())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
let manifest_version = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest().version())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
let config_actions = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.and_then(|x| x.manifest().config())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
interface_addresses_receipt: interface_addresses_receipt(skeleton_key)?,
|
||||
manifest_volumes: manifest_volumes.verify(skeleton_key)?,
|
||||
config_actions: config_actions.verify(skeleton_key)?,
|
||||
manifest_version: manifest_version.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct ConfigPointer {
|
||||
@@ -1940,25 +1766,38 @@ impl ConfigPointer {
|
||||
pub fn select(&self, val: &Value) -> Value {
|
||||
self.selector.select(self.multi, val)
|
||||
}
|
||||
async fn deref<Db: DbHandle>(
|
||||
async fn deref(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
config_overrides: &BTreeMap<PackageId, Config>,
|
||||
receipts: &ConfigPointerReceipts,
|
||||
) -> Result<Value, ConfigurationError> {
|
||||
if let Some(cfg) = config_overrides.get(&self.package_id) {
|
||||
Ok(self.select(&Value::Object(cfg.clone())))
|
||||
} else {
|
||||
let id = &self.package_id;
|
||||
let version = receipts.manifest_version.get(db, id).await.ok().flatten();
|
||||
let cfg_actions = receipts.config_actions.get(db, id).await.ok().flatten();
|
||||
let volumes = receipts.manifest_volumes.get(db, id).await.ok().flatten();
|
||||
if let (Some(version), Some(cfg_actions), Some(volumes)) =
|
||||
(&version, &cfg_actions, &volumes)
|
||||
{
|
||||
let db = ctx
|
||||
.db
|
||||
.peek()
|
||||
.await
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?;
|
||||
let manifest = db.as_package_data().as_idx(id).map(|pde| pde.as_manifest());
|
||||
let cfg_actions = manifest.and_then(|m| m.as_config().transpose_ref());
|
||||
if let (Some(manifest), Some(cfg_actions)) = (manifest, cfg_actions) {
|
||||
let cfg_res = cfg_actions
|
||||
.get(ctx, &self.package_id, version, volumes)
|
||||
.de()
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?
|
||||
.get(
|
||||
ctx,
|
||||
&self.package_id,
|
||||
&manifest
|
||||
.as_version()
|
||||
.de()
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?,
|
||||
&manifest
|
||||
.as_volumes()
|
||||
.de()
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ConfigurationError::SystemError(e))?;
|
||||
if let Some(cfg) = cfg_res.config {
|
||||
@@ -1990,7 +1829,7 @@ pub struct ConfigSelector {
|
||||
}
|
||||
impl ConfigSelector {
|
||||
fn select(&self, multi: bool, val: &Value) -> Value {
|
||||
let selected = self.compiled.select(&val).ok().unwrap_or_else(Vec::new);
|
||||
let selected = self.compiled.select(&val).ok().unwrap_or_else(Vector::new);
|
||||
if multi {
|
||||
Value::Array(selected.into_iter().cloned().collect())
|
||||
} else {
|
||||
@@ -2069,10 +1908,10 @@ impl TorKeyPointer {
|
||||
)
|
||||
.await
|
||||
.map_err(ConfigurationError::SystemError)?;
|
||||
Ok(Value::String(base32::encode(
|
||||
Ok(Value::String(Arc::new(base32::encode(
|
||||
base32::Alphabet::RFC4648 { padding: false },
|
||||
&key.tor_key().as_bytes(),
|
||||
)))
|
||||
))))
|
||||
}
|
||||
}
|
||||
impl fmt::Display for TorKeyPointer {
|
||||
@@ -2092,7 +1931,7 @@ impl fmt::Display for SystemPointerSpec {
|
||||
}
|
||||
}
|
||||
impl SystemPointerSpec {
|
||||
async fn deref<Db: DbHandle>(&self, _db: &mut Db) -> Result<Value, ConfigurationError> {
|
||||
async fn deref(&self, _ctx: &RpcContext) -> Result<Value, ConfigurationError> {
|
||||
#[allow(unreachable_code)]
|
||||
Ok(match *self {})
|
||||
}
|
||||
@@ -2115,17 +1954,14 @@ impl ValueSpec for SystemPointerSpec {
|
||||
fn validate(&self, _manifest: &Manifest) -> Result<(), NoMatchWithPath> {
|
||||
Ok(())
|
||||
}
|
||||
async fn update<Db: DbHandle>(
|
||||
async fn update(
|
||||
&self,
|
||||
_ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
ctx: &RpcContext,
|
||||
_manifest: &Manifest,
|
||||
_config_overrides: &BTreeMap<PackageId, Config>,
|
||||
value: &mut Value,
|
||||
|
||||
_receipts: &ConfigPointerReceipts,
|
||||
) -> Result<(), ConfigurationError> {
|
||||
*value = self.deref(db).await?;
|
||||
*value = self.deref(ctx).await?;
|
||||
Ok(())
|
||||
}
|
||||
fn pointers(&self, _value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::borrow::Cow;
|
||||
use std::ops::{Bound, RangeBounds, RangeInclusive};
|
||||
|
||||
use patch_db::Value;
|
||||
use rand::distributions::Distribution;
|
||||
use rand::Rng;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::Config;
|
||||
|
||||
@@ -321,7 +321,7 @@ impl UniqueBy {
|
||||
match self {
|
||||
UniqueBy::Any(any) => any.iter().any(|u| u.eq(lhs, rhs)),
|
||||
UniqueBy::All(all) => all.iter().all(|u| u.eq(lhs, rhs)),
|
||||
UniqueBy::Exactly(key) => lhs.get(key) == rhs.get(key),
|
||||
UniqueBy::Exactly(key) => lhs.get(&**key) == rhs.get(&**key),
|
||||
UniqueBy::NotUnique => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
use helpers::to_tmp_path;
|
||||
use josekit::jwk::Jwk;
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::{DbHandle, LockReceipt, LockType, PatchDb};
|
||||
use patch_db::PatchDb;
|
||||
use reqwest::{Client, Proxy, Url};
|
||||
use rpc_toolkit::Context;
|
||||
use serde::Deserialize;
|
||||
@@ -20,10 +20,11 @@ use tracing::instrument;
|
||||
use super::setup::CURRENT_SECRET;
|
||||
use crate::account::AccountInfo;
|
||||
use crate::core::rpc_continuations::{RequestGuid, RestHandler, RpcContinuation};
|
||||
use crate::db::model::{CurrentDependents, Database, InstalledPackageDataEntry, PackageDataEntry};
|
||||
use crate::db::model::{CurrentDependents, Database, PackageDataEntryMatchModelRef};
|
||||
use crate::db::prelude::PatchDbExt;
|
||||
use crate::disk::OsPartitionInfo;
|
||||
use crate::init::init_postgres;
|
||||
use crate::install::cleanup::{cleanup_failed, uninstall, CleanupFailedReceipts};
|
||||
use crate::install::cleanup::{cleanup_failed, uninstall};
|
||||
use crate::manager::ManagerMap;
|
||||
use crate::middleware::auth::HashSessionToken;
|
||||
use crate::net::net_controller::NetController;
|
||||
@@ -31,7 +32,7 @@ use crate::net::ssl::SslManager;
|
||||
use crate::net::wifi::WpaCli;
|
||||
use crate::notifications::NotificationManager;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::status::{MainStatus, Status};
|
||||
use crate::status::MainStatus;
|
||||
use crate::system::get_mem_info;
|
||||
use crate::util::config::load_config_from_paths;
|
||||
use crate::util::lshw::{lshw, LshwDevice};
|
||||
@@ -128,49 +129,11 @@ pub struct Hardware {
|
||||
pub ram: u64,
|
||||
}
|
||||
|
||||
pub struct RpcCleanReceipts {
|
||||
cleanup_receipts: CleanupFailedReceipts,
|
||||
packages: LockReceipt<crate::db::model::AllPackageData, ()>,
|
||||
package: LockReceipt<crate::db::model::PackageDataEntry, String>,
|
||||
}
|
||||
|
||||
impl RpcCleanReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let cleanup_receipts = CleanupFailedReceipts::setup(locks);
|
||||
|
||||
let packages = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let package = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
cleanup_receipts: cleanup_receipts(skeleton_key)?,
|
||||
packages: packages.verify(skeleton_key)?,
|
||||
package: package.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RpcContext(Arc<RpcContextSeed>);
|
||||
impl RpcContext {
|
||||
#[instrument(skip_all)]
|
||||
pub async fn init<P: AsRef<Path> + Send + 'static>(
|
||||
pub async fn init<P: AsRef<Path> + Send + Sync + 'static>(
|
||||
cfg_path: Option<P>,
|
||||
disk_guid: Arc<String>,
|
||||
) -> Result<Self, Error> {
|
||||
@@ -203,7 +166,7 @@ impl RpcContext {
|
||||
);
|
||||
tracing::info!("Initialized Net Controller");
|
||||
let managers = ManagerMap::default();
|
||||
let metrics_cache = RwLock::new(None);
|
||||
let metrics_cache = RwLock::<Option<crate::system::Metrics>>::new(None);
|
||||
let notification_manager = NotificationManager::new(secret_store.clone());
|
||||
tracing::info!("Initialized Notification Manager");
|
||||
let tor_proxy_url = format!("socks5h://{tor_proxy}");
|
||||
@@ -253,16 +216,11 @@ impl RpcContext {
|
||||
hardware: Hardware { devices, ram },
|
||||
});
|
||||
|
||||
let res = Self(seed);
|
||||
let res = Self(seed.clone());
|
||||
res.cleanup().await?;
|
||||
tracing::info!("Cleaned up transient states");
|
||||
res.managers
|
||||
.init(
|
||||
&res,
|
||||
&mut res.db.handle(),
|
||||
&mut res.secret_store.acquire().await?,
|
||||
)
|
||||
.await?;
|
||||
let peeked = res.db.peek().await?;
|
||||
res.managers.init(res.clone(), peeked).await?;
|
||||
tracing::info!("Initialized Package Managers");
|
||||
Ok(res)
|
||||
}
|
||||
@@ -277,118 +235,103 @@ impl RpcContext {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
#[instrument(skip(self))]
|
||||
pub async fn cleanup(&self) -> Result<(), Error> {
|
||||
let mut db = self.db.handle();
|
||||
let receipts = RpcCleanReceipts::new(&mut db).await?;
|
||||
let packages = receipts.packages.get(&mut db).await?.0;
|
||||
let mut current_dependents = packages
|
||||
.keys()
|
||||
.map(|k| (k.clone(), BTreeMap::new()))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
for (package_id, package) in packages {
|
||||
for (k, v) in package
|
||||
.into_installed()
|
||||
.into_iter()
|
||||
.flat_map(|i| i.current_dependencies.0)
|
||||
{
|
||||
let mut entry: BTreeMap<_, _> = current_dependents.remove(&k).unwrap_or_default();
|
||||
entry.insert(package_id.clone(), v);
|
||||
current_dependents.insert(k, entry);
|
||||
}
|
||||
}
|
||||
for (package_id, current_dependents) in current_dependents {
|
||||
if let Some(deps) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&package_id)
|
||||
.and_then(|pde| pde.installed())
|
||||
.map::<_, CurrentDependents>(|i| i.current_dependents())
|
||||
.check(&mut db)
|
||||
.await?
|
||||
{
|
||||
deps.put(&mut db, &CurrentDependents(current_dependents))
|
||||
.await?;
|
||||
} else if let Some(deps) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&package_id)
|
||||
.and_then(|pde| pde.removing())
|
||||
.map::<_, CurrentDependents>(|i| i.current_dependents())
|
||||
.check(&mut db)
|
||||
.await?
|
||||
{
|
||||
deps.put(&mut db, &CurrentDependents(current_dependents))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
for (package_id, package) in receipts.packages.get(&mut db).await?.0 {
|
||||
if let Err(e) = async {
|
||||
match package {
|
||||
PackageDataEntry::Installing { .. }
|
||||
| PackageDataEntry::Restoring { .. }
|
||||
| PackageDataEntry::Updating { .. } => {
|
||||
cleanup_failed(self, &mut db, &package_id, &receipts.cleanup_receipts)
|
||||
.await?;
|
||||
}
|
||||
PackageDataEntry::Removing { .. } => {
|
||||
uninstall(
|
||||
self,
|
||||
&mut db,
|
||||
&mut self.secret_store.acquire().await?,
|
||||
&package_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
PackageDataEntry::Installed {
|
||||
installed,
|
||||
static_files,
|
||||
manifest,
|
||||
} => {
|
||||
for (volume_id, volume_info) in &*manifest.volumes {
|
||||
let tmp_path = to_tmp_path(volume_info.path_for(
|
||||
&self.datadir,
|
||||
&package_id,
|
||||
&manifest.version,
|
||||
&volume_id,
|
||||
))
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
if tokio::fs::metadata(&tmp_path).await.is_ok() {
|
||||
tokio::fs::remove_dir_all(&tmp_path).await?;
|
||||
}
|
||||
}
|
||||
let status = installed.status;
|
||||
let main = match status.main {
|
||||
MainStatus::BackingUp { started, .. } => {
|
||||
if let Some(_) = started {
|
||||
MainStatus::Starting
|
||||
} else {
|
||||
MainStatus::Stopped
|
||||
}
|
||||
}
|
||||
MainStatus::Running { .. } => MainStatus::Starting,
|
||||
a => a.clone(),
|
||||
};
|
||||
let new_package = PackageDataEntry::Installed {
|
||||
installed: InstalledPackageDataEntry {
|
||||
status: Status { main, ..status },
|
||||
..installed
|
||||
},
|
||||
static_files,
|
||||
manifest,
|
||||
};
|
||||
receipts
|
||||
.package
|
||||
.set(&mut db, new_package, &package_id)
|
||||
.await?;
|
||||
self.db
|
||||
.mutate(|f| {
|
||||
let mut current_dependents = f
|
||||
.as_package_data()
|
||||
.keys()?
|
||||
.into_iter()
|
||||
.map(|k| (k.clone(), BTreeMap::new()))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
for (package_id, package) in f.as_package_data_mut().as_entries_mut()? {
|
||||
for (k, v) in package
|
||||
.as_installed_mut()
|
||||
.into_iter()
|
||||
.flat_map(|i| i.clone().into_current_dependencies().into_entries())
|
||||
.flatten()
|
||||
{
|
||||
let mut entry: BTreeMap<_, _> =
|
||||
current_dependents.remove(&k).unwrap_or_default();
|
||||
entry.insert(package_id.clone(), v.de()?);
|
||||
current_dependents.insert(k, entry);
|
||||
}
|
||||
}
|
||||
Ok::<_, Error>(())
|
||||
}
|
||||
.await
|
||||
{
|
||||
for (package_id, current_dependents) in current_dependents {
|
||||
if let Some(deps) = f
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&package_id)
|
||||
.and_then(|pde| pde.expect_as_installed_mut().ok())
|
||||
.map(|i| i.as_installed_mut().as_current_dependents_mut())
|
||||
{
|
||||
deps.ser(&CurrentDependents(current_dependents))?;
|
||||
} else if let Some(deps) = f
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&package_id)
|
||||
.and_then(|pde| pde.expect_as_removing_mut().ok())
|
||||
.map(|i| i.as_removing_mut().as_current_dependents_mut())
|
||||
{
|
||||
deps.ser(&CurrentDependents(current_dependents))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
let peek = self.db.peek().await?;
|
||||
for (package_id, package) in peek.as_package_data().as_entries()?.into_iter() {
|
||||
let package = package.clone();
|
||||
let action = match package.as_match() {
|
||||
PackageDataEntryMatchModelRef::Installing(_)
|
||||
| PackageDataEntryMatchModelRef::Restoring(_)
|
||||
| PackageDataEntryMatchModelRef::Updating(_) => {
|
||||
cleanup_failed(self, &package_id).await
|
||||
}
|
||||
PackageDataEntryMatchModelRef::Removing(_) => {
|
||||
uninstall(self, &mut self.secret_store.acquire().await?, &package_id).await
|
||||
}
|
||||
PackageDataEntryMatchModelRef::Installed(m) => {
|
||||
let version = m.as_manifest().as_version().clone().de()?;
|
||||
let volumes = m.as_manifest().as_volumes().de()?;
|
||||
for (volume_id, volume_info) in &*volumes {
|
||||
let tmp_path = to_tmp_path(volume_info.path_for(
|
||||
&self.datadir,
|
||||
&package_id,
|
||||
&version,
|
||||
&volume_id,
|
||||
))
|
||||
.with_kind(ErrorKind::Filesystem)?;
|
||||
if tokio::fs::metadata(&tmp_path).await.is_ok() {
|
||||
tokio::fs::remove_dir_all(&tmp_path).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
if let Err(e) = action {
|
||||
tracing::error!("Failed to clean up package {}: {}", package_id, e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
}
|
||||
self.db
|
||||
.mutate(|v| {
|
||||
for (_, pde) in v.as_package_data_mut().as_entries_mut()? {
|
||||
let status = pde
|
||||
.expect_as_installed_mut()?
|
||||
.as_installed_mut()
|
||||
.as_status_mut()
|
||||
.as_main_mut();
|
||||
let running = status.clone().de()?.running();
|
||||
status.ser(&if running {
|
||||
MainStatus::Starting
|
||||
} else {
|
||||
MainStatus::Stopped
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,77 +1,27 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use patch_db::{DbHandle, LockReceipt, LockType};
|
||||
use rpc_toolkit::command;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::dependencies::{
|
||||
break_all_dependents_transitive, heal_all_dependents_transitive, BreakageRes, DependencyError,
|
||||
DependencyReceipt, TaggedDependencyError,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::status::MainStatus;
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::display_serializable;
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StartReceipts {
|
||||
dependency_receipt: DependencyReceipt,
|
||||
status: LockReceipt<MainStatus, ()>,
|
||||
version: LockReceipt<crate::util::Version, ()>,
|
||||
}
|
||||
|
||||
impl StartReceipts {
|
||||
pub async fn new(db: &mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks, id);
|
||||
setup(&db.lock_all(locks).await?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
id: &PackageId,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let dependency_receipt = DependencyReceipt::setup(locks);
|
||||
let status = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.status().main())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let version = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.manifest().version())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
dependency_receipt: dependency_receipt(skeleton_key)?,
|
||||
status: status.verify(skeleton_key)?,
|
||||
version: version.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[command(display(display_none), metadata(sync_db = true))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(), Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut tx = db.begin().await?;
|
||||
let receipts = StartReceipts::new(&mut tx, &id).await?;
|
||||
let version = receipts.version.get(&mut tx).await?;
|
||||
receipts.status.set(&mut tx, MainStatus::Starting).await?;
|
||||
heal_all_dependents_transitive(&ctx, &mut tx, &id, &receipts.dependency_receipt).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
drop(receipts);
|
||||
let peek = ctx.db.peek().await?;
|
||||
let version = peek
|
||||
.as_package_data()
|
||||
.as_idx(&id)
|
||||
.or_not_found(&id)?
|
||||
.as_installed()
|
||||
.or_not_found(&id)?
|
||||
.as_manifest()
|
||||
.as_version()
|
||||
.de()?;
|
||||
|
||||
ctx.managers
|
||||
.get(&(id, version))
|
||||
@@ -81,112 +31,33 @@ pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct StopReceipts {
|
||||
breaks: crate::dependencies::BreakTransitiveReceipts,
|
||||
status: LockReceipt<MainStatus, ()>,
|
||||
}
|
||||
|
||||
impl StopReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
#[command(display(display_none), metadata(sync_db = true))]
|
||||
pub async fn stop(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<MainStatus, Error> {
|
||||
let peek = ctx.db.peek().await?;
|
||||
let version = peek
|
||||
.as_package_data()
|
||||
.as_idx(&id)
|
||||
.or_not_found(&id)?
|
||||
.as_installed()
|
||||
.or_not_found(&id)?
|
||||
.as_manifest()
|
||||
.as_version()
|
||||
.de()?;
|
||||
|
||||
let setup = Self::setup(&mut locks, id);
|
||||
setup(&db.lock_all(locks).await?)
|
||||
}
|
||||
let last_statuts = ctx
|
||||
.db
|
||||
.mutate(|v| {
|
||||
v.as_package_data_mut()
|
||||
.as_idx_mut(&id)
|
||||
.and_then(|x| x.as_installed_mut())
|
||||
.ok_or_else(|| Error::new(eyre!("{} is not installed", id), ErrorKind::NotFound))?
|
||||
.as_status_mut()
|
||||
.as_main_mut()
|
||||
.replace(&MainStatus::Stopping)
|
||||
})
|
||||
.await?;
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
id: &PackageId,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let breaks = crate::dependencies::BreakTransitiveReceipts::setup(locks);
|
||||
let status = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.status().main())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
breaks: breaks(skeleton_key)?,
|
||||
status: status.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn stop_common<Db: DbHandle>(
|
||||
db: &mut Db,
|
||||
id: &PackageId,
|
||||
breakages: &mut BTreeMap<PackageId, TaggedDependencyError>,
|
||||
) -> Result<MainStatus, Error> {
|
||||
let mut tx = db.begin().await?;
|
||||
let receipts = StopReceipts::new(&mut tx, id).await?;
|
||||
let last_status = receipts.status.get(&mut tx).await?;
|
||||
receipts.status.set(&mut tx, MainStatus::Stopping).await?;
|
||||
|
||||
tx.save().await?;
|
||||
break_all_dependents_transitive(
|
||||
db,
|
||||
id,
|
||||
DependencyError::NotRunning,
|
||||
breakages,
|
||||
&receipts.breaks,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(last_status)
|
||||
}
|
||||
|
||||
#[command(
|
||||
subcommands(self(stop_impl(async)), stop_dry),
|
||||
display(display_none),
|
||||
metadata(sync_db = true)
|
||||
)]
|
||||
pub fn stop(#[arg] id: PackageId) -> Result<PackageId, Error> {
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
#[command(rename = "dry", display(display_serializable))]
|
||||
#[instrument(skip_all)]
|
||||
pub async fn stop_dry(
|
||||
#[context] ctx: RpcContext,
|
||||
#[parent_data] id: PackageId,
|
||||
) -> Result<BreakageRes, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let mut breakages = BTreeMap::new();
|
||||
stop_common(&mut tx, &id, &mut breakages).await?;
|
||||
|
||||
tx.abort().await?;
|
||||
|
||||
Ok(BreakageRes(breakages))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result<MainStatus, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut tx = db.begin().await?;
|
||||
let version = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&id)
|
||||
.expect(&mut tx)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(&mut tx)
|
||||
.await?
|
||||
.manifest()
|
||||
.version()
|
||||
.get(&mut tx)
|
||||
.await?
|
||||
.clone();
|
||||
|
||||
let last_statuts = stop_common(&mut tx, &id, &mut BTreeMap::new()).await?;
|
||||
|
||||
tx.commit().await?;
|
||||
ctx.managers
|
||||
.get(&(id, version))
|
||||
.await
|
||||
@@ -198,30 +69,21 @@ pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result<MainStatus, Err
|
||||
|
||||
#[command(display(display_none), metadata(sync_db = true))]
|
||||
pub async fn restart(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(), Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut tx = db.begin().await?;
|
||||
let version = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&id)
|
||||
.expect(&mut tx)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(&mut tx)
|
||||
.await?
|
||||
.manifest()
|
||||
.version()
|
||||
.get(&mut tx)
|
||||
.await?
|
||||
.clone();
|
||||
|
||||
tx.commit().await?;
|
||||
let peek = ctx.db.peek().await?;
|
||||
let version = peek
|
||||
.as_package_data()
|
||||
.as_idx(&id)
|
||||
.or_not_found(&id)?
|
||||
.expect_as_installed()?
|
||||
.as_manifest()
|
||||
.as_version()
|
||||
.de()?;
|
||||
|
||||
ctx.managers
|
||||
.get(&(id, version))
|
||||
.await
|
||||
.ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))?
|
||||
.restart()
|
||||
.await;
|
||||
.restart();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
pub mod model;
|
||||
pub mod package;
|
||||
pub mod prelude;
|
||||
|
||||
use std::future::Future;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::{DbHandle, Dump, LockType, Revision};
|
||||
use patch_db::{Dump, Revision};
|
||||
use rpc_toolkit::command;
|
||||
use rpc_toolkit::hyper::upgrade::Upgraded;
|
||||
use rpc_toolkit::hyper::{Body, Error as HyperError, Request, Response};
|
||||
@@ -22,12 +23,11 @@ use tokio_tungstenite::tungstenite::Message;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use tracing::instrument;
|
||||
|
||||
pub use self::model::DatabaseModel;
|
||||
use crate::context::RpcContext;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::middleware::auth::{HasValidSession, HashSessionToken};
|
||||
use crate::prelude::*;
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::{display_serializable, IoFormat};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn ws_handler<
|
||||
@@ -40,8 +40,8 @@ async fn ws_handler<
|
||||
let (dump, sub) = ctx.db.dump_and_sub().await?;
|
||||
let mut stream = ws_fut
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Network)?
|
||||
.with_kind(crate::ErrorKind::Unknown)?;
|
||||
.with_kind(ErrorKind::Network)?
|
||||
.with_kind(ErrorKind::Unknown)?;
|
||||
|
||||
if let Some((session, token)) = session {
|
||||
let kill = subscribe_to_session_kill(&ctx, token).await;
|
||||
@@ -55,7 +55,7 @@ async fn ws_handler<
|
||||
reason: "UNAUTHORIZED".into(),
|
||||
}))
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Network)?;
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -82,7 +82,6 @@ async fn deal_with_messages(
|
||||
mut sub: patch_db::Subscriber,
|
||||
mut stream: WebSocketStream<Upgraded>,
|
||||
) -> Result<(), Error> {
|
||||
let mut timer = tokio::time::interval(tokio::time::Duration::from_secs(5));
|
||||
loop {
|
||||
futures::select! {
|
||||
_ = (&mut kill).fuse() => {
|
||||
@@ -93,31 +92,26 @@ async fn deal_with_messages(
|
||||
reason: "UNAUTHORIZED".into(),
|
||||
}))
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Network)?;
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
return Ok(())
|
||||
}
|
||||
new_rev = sub.recv().fuse() => {
|
||||
let rev = new_rev.expect("UNREACHABLE: patch-db is dropped");
|
||||
stream
|
||||
.send(Message::Text(serde_json::to_string(&rev).with_kind(crate::ErrorKind::Serialization)?))
|
||||
.send(Message::Text(serde_json::to_string(&rev).with_kind(ErrorKind::Serialization)?))
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Network)?;
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
}
|
||||
message = stream.next().fuse() => {
|
||||
let message = message.transpose().with_kind(crate::ErrorKind::Network)?;
|
||||
if message.is_none() {
|
||||
tracing::info!("Closing WebSocket: Stream Finished");
|
||||
return Ok(())
|
||||
let message = message.transpose().with_kind(ErrorKind::Network)?;
|
||||
match message {
|
||||
None => {
|
||||
tracing::info!("Closing WebSocket: Stream Finished");
|
||||
return Ok(())
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
// This is trying to give a health checks to the home to keep the ui alive.
|
||||
_ = timer.tick().fuse() => {
|
||||
stream
|
||||
.send(Message::Ping(vec![]))
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Network)?;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,10 +123,10 @@ async fn send_dump(
|
||||
) -> Result<(), Error> {
|
||||
stream
|
||||
.send(Message::Text(
|
||||
serde_json::to_string(&dump).with_kind(crate::ErrorKind::Serialization)?,
|
||||
serde_json::to_string(&dump).with_kind(ErrorKind::Serialization)?,
|
||||
))
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Network)?;
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -147,7 +141,7 @@ pub async fn subscribe(ctx: RpcContext, req: Request<Body>) -> Result<Response<B
|
||||
{
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
if e.kind != crate::ErrorKind::Authorization {
|
||||
if e.kind != ErrorKind::Authorization {
|
||||
tracing::error!("Error Authenticating Websocket: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
@@ -155,7 +149,7 @@ pub async fn subscribe(ctx: RpcContext, req: Request<Body>) -> Result<Response<B
|
||||
}
|
||||
};
|
||||
let req = Request::from_parts(parts, body);
|
||||
let (res, ws_fut) = hyper_ws_listener::create_ws(req).with_kind(crate::ErrorKind::Network)?;
|
||||
let (res, ws_fut) = hyper_ws_listener::create_ws(req).with_kind(ErrorKind::Network)?;
|
||||
if let Some(ws_fut) = ws_fut {
|
||||
tokio::task::spawn(async move {
|
||||
match ws_handler(ctx, session, ws_fut).await {
|
||||
@@ -197,12 +191,40 @@ pub async fn revisions(
|
||||
})
|
||||
}
|
||||
|
||||
#[command(display(display_serializable))]
|
||||
#[instrument(skip_all)]
|
||||
async fn cli_dump(
|
||||
ctx: CliContext,
|
||||
_format: Option<IoFormat>,
|
||||
path: Option<PathBuf>,
|
||||
) -> Result<Dump, RpcError> {
|
||||
let dump = if let Some(path) = path {
|
||||
PatchDb::open(path).await?.dump().await?
|
||||
} else {
|
||||
rpc_toolkit::command_helpers::call_remote(
|
||||
ctx,
|
||||
"db.dump",
|
||||
serde_json::json!({}),
|
||||
std::marker::PhantomData::<Dump>,
|
||||
)
|
||||
.await?
|
||||
.result?
|
||||
};
|
||||
|
||||
Ok(dump)
|
||||
}
|
||||
|
||||
#[command(
|
||||
custom_cli(cli_dump(async, context(CliContext))),
|
||||
display(display_serializable)
|
||||
)]
|
||||
pub async fn dump(
|
||||
#[context] ctx: RpcContext,
|
||||
#[allow(unused_variables)]
|
||||
#[arg(long = "format")]
|
||||
format: Option<IoFormat>,
|
||||
#[allow(unused_variables)]
|
||||
#[arg]
|
||||
path: Option<PathBuf>,
|
||||
) -> Result<Dump, Error> {
|
||||
Ok(ctx.db.dump().await?)
|
||||
}
|
||||
@@ -258,34 +280,77 @@ fn apply_expr(input: jaq_core::Val, expr: &str) -> Result<jaq_core::Val, Error>
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[command(display(display_none))]
|
||||
pub async fn apply(#[context] ctx: RpcContext, #[arg] expr: String) -> Result<(), Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
#[instrument(skip_all)]
|
||||
async fn cli_apply(ctx: CliContext, expr: String, path: Option<PathBuf>) -> Result<(), RpcError> {
|
||||
if let Some(path) = path {
|
||||
PatchDb::open(path)
|
||||
.await?
|
||||
.mutate(|db| {
|
||||
let res = apply_expr(
|
||||
serde_json::to_value(patch_db::Value::from(db.clone()))
|
||||
.with_kind(ErrorKind::Deserialization)?
|
||||
.into(),
|
||||
&expr,
|
||||
)?;
|
||||
|
||||
DatabaseModel::new().lock(&mut db, LockType::Write).await?;
|
||||
|
||||
let root_ptr = JsonPointer::<String>::default();
|
||||
|
||||
let input = db.get_value(&root_ptr, None).await?;
|
||||
|
||||
let res = (|| {
|
||||
let res = apply_expr(input.into(), &expr)?;
|
||||
|
||||
serde_json::from_value::<model::Database>(res.clone().into()).with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Deserialization,
|
||||
"result does not match database model",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok::<serde_json::Value, Error>(res.into())
|
||||
})()?;
|
||||
|
||||
db.put_value(&root_ptr, &res).await?;
|
||||
db.ser(
|
||||
&serde_json::from_value::<model::Database>(res.clone().into()).with_ctx(
|
||||
|_| {
|
||||
(
|
||||
crate::ErrorKind::Deserialization,
|
||||
"result does not match database model",
|
||||
)
|
||||
},
|
||||
)?,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
} else {
|
||||
rpc_toolkit::command_helpers::call_remote(
|
||||
ctx,
|
||||
"db.apply",
|
||||
serde_json::json!({ "expr": expr }),
|
||||
std::marker::PhantomData::<()>,
|
||||
)
|
||||
.await?
|
||||
.result?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command(
|
||||
custom_cli(cli_apply(async, context(CliContext))),
|
||||
display(display_none)
|
||||
)]
|
||||
pub async fn apply(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg] expr: String,
|
||||
#[allow(unused_variables)]
|
||||
#[arg]
|
||||
path: Option<PathBuf>,
|
||||
) -> Result<(), Error> {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
let res = apply_expr(
|
||||
serde_json::to_value(patch_db::Value::from(db.clone()))
|
||||
.with_kind(ErrorKind::Deserialization)?
|
||||
.into(),
|
||||
&expr,
|
||||
)?;
|
||||
|
||||
db.ser(
|
||||
&serde_json::from_value::<model::Database>(res.clone().into()).with_ctx(|_| {
|
||||
(
|
||||
crate::ErrorKind::Deserialization,
|
||||
"result does not match database model",
|
||||
)
|
||||
})?,
|
||||
)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[command(subcommands(ui))]
|
||||
pub fn put() -> Result<(), RpcError> {
|
||||
Ok(())
|
||||
@@ -303,7 +368,7 @@ pub async fn ui(
|
||||
) -> Result<(), Error> {
|
||||
let ptr = "/ui"
|
||||
.parse::<JsonPointer>()
|
||||
.with_kind(crate::ErrorKind::Database)?
|
||||
.with_kind(ErrorKind::Database)?
|
||||
+ &pointer;
|
||||
ctx.db.put(&ptr, &value).await?;
|
||||
Ok(())
|
||||
|
||||
@@ -7,49 +7,45 @@ use emver::VersionRange;
|
||||
use ipnet::{Ipv4Net, Ipv6Net};
|
||||
use isocountry::CountryCode;
|
||||
use itertools::Itertools;
|
||||
use models::{DataUrl, HealthCheckId, InterfaceId};
|
||||
use openssl::hash::MessageDigest;
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::{HasModel, Map, MapModel, OptionModel};
|
||||
use patch_db::{HasModel, Value};
|
||||
use reqwest::Url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use ssh_key::public::Ed25519PublicKey;
|
||||
|
||||
use crate::account::AccountInfo;
|
||||
use crate::config::spec::{PackagePointerSpec, SystemPointerSpec};
|
||||
use crate::config::spec::PackagePointerSpec;
|
||||
use crate::install::progress::InstallProgress;
|
||||
use crate::net::interface::InterfaceId;
|
||||
use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr};
|
||||
use crate::s9pk::manifest::{Manifest, ManifestModel, PackageId};
|
||||
use crate::status::health_check::HealthCheckId;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::status::Status;
|
||||
use crate::util::Version;
|
||||
use crate::version::{Current, VersionT};
|
||||
use crate::Error;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
// #[macro_debug]
|
||||
pub struct Database {
|
||||
#[model]
|
||||
pub server_info: ServerInfo,
|
||||
#[model]
|
||||
pub package_data: AllPackageData,
|
||||
pub ui: Value,
|
||||
}
|
||||
impl Database {
|
||||
pub fn init(account: &AccountInfo) -> Self {
|
||||
let lan_address = account.hostname.lan_address().parse().unwrap();
|
||||
// TODO
|
||||
Database {
|
||||
server_info: ServerInfo {
|
||||
id: account.server_id.clone(),
|
||||
version: Current::new().semver().into(),
|
||||
hostname: Some(account.hostname.no_dot_host_name()),
|
||||
hostname: account.hostname.no_dot_host_name(),
|
||||
last_backup: None,
|
||||
last_wifi_region: None,
|
||||
eos_version_compat: Current::new().compat().clone(),
|
||||
lan_address,
|
||||
tor_address: format!("https://{}", account.key.tor_address())
|
||||
tor_address: format!("http://{}", account.key.tor_address())
|
||||
.parse()
|
||||
.unwrap(),
|
||||
ip_info: BTreeMap::new(),
|
||||
@@ -88,17 +84,15 @@ impl Database {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl DatabaseModel {
|
||||
pub fn new() -> Self {
|
||||
Self::from(JsonPointer::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub type DatabaseModel = Model<Database>;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct ServerInfo {
|
||||
pub id: String,
|
||||
pub hostname: Option<String>,
|
||||
pub hostname: String,
|
||||
pub version: Version,
|
||||
pub last_backup: Option<DateTime<Utc>>,
|
||||
/// Used in the wifi to determine the region to set the system to
|
||||
@@ -106,10 +100,7 @@ pub struct ServerInfo {
|
||||
pub eos_version_compat: VersionRange,
|
||||
pub lan_address: Url,
|
||||
pub tor_address: Url,
|
||||
#[model]
|
||||
#[serde(default)]
|
||||
pub ip_info: BTreeMap<String, IpInfo>,
|
||||
#[model]
|
||||
#[serde(default)]
|
||||
pub status_info: ServerStatus,
|
||||
pub wifi: WifiInfo,
|
||||
@@ -125,6 +116,7 @@ pub struct ServerInfo {
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct IpInfo {
|
||||
pub ipv4_range: Option<Ipv4Net>,
|
||||
pub ipv4: Option<Ipv4Addr>,
|
||||
@@ -145,29 +137,31 @@ impl IpInfo {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct BackupProgress {
|
||||
pub complete: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct ServerStatus {
|
||||
#[model]
|
||||
pub backup_progress: Option<BTreeMap<PackageId, BackupProgress>>,
|
||||
pub updated: bool,
|
||||
#[model]
|
||||
pub update_progress: Option<UpdateProgress>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct UpdateProgress {
|
||||
pub size: Option<u64>,
|
||||
pub downloaded: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct WifiInfo {
|
||||
pub ssids: Vec<String>,
|
||||
pub selected: Option<String>,
|
||||
@@ -194,16 +188,11 @@ pub struct AllPackageData(pub BTreeMap<PackageId, PackageDataEntry>);
|
||||
impl Map for AllPackageData {
|
||||
type Key = PackageId;
|
||||
type Value = PackageDataEntry;
|
||||
fn get(&self, key: &Self::Key) -> Option<&Self::Value> {
|
||||
self.0.get(key)
|
||||
}
|
||||
}
|
||||
impl HasModel for AllPackageData {
|
||||
type Model = MapModel<Self>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct StaticFiles {
|
||||
license: String,
|
||||
instructions: String,
|
||||
@@ -219,120 +208,231 @@ impl StaticFiles {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct PackageDataEntryInstalling {
|
||||
pub static_files: StaticFiles,
|
||||
pub manifest: Manifest,
|
||||
pub install_progress: Arc<InstallProgress>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct PackageDataEntryUpdating {
|
||||
pub static_files: StaticFiles,
|
||||
pub manifest: Manifest,
|
||||
pub installed: InstalledPackageInfo,
|
||||
pub install_progress: Arc<InstallProgress>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct PackageDataEntryRestoring {
|
||||
pub static_files: StaticFiles,
|
||||
pub manifest: Manifest,
|
||||
pub install_progress: Arc<InstallProgress>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct PackageDataEntryRemoving {
|
||||
pub static_files: StaticFiles,
|
||||
pub manifest: Manifest,
|
||||
pub removing: InstalledPackageInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct PackageDataEntryInstalled {
|
||||
pub static_files: StaticFiles,
|
||||
pub manifest: Manifest,
|
||||
pub installed: InstalledPackageInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(tag = "state")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
// #[macro_debug]
|
||||
pub enum PackageDataEntry {
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
Installing {
|
||||
static_files: StaticFiles,
|
||||
manifest: Manifest,
|
||||
install_progress: Arc<InstallProgress>,
|
||||
},
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
Updating {
|
||||
static_files: StaticFiles,
|
||||
manifest: Manifest,
|
||||
installed: InstalledPackageDataEntry,
|
||||
install_progress: Arc<InstallProgress>,
|
||||
},
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
Restoring {
|
||||
static_files: StaticFiles,
|
||||
manifest: Manifest,
|
||||
install_progress: Arc<InstallProgress>,
|
||||
},
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
Removing {
|
||||
static_files: StaticFiles,
|
||||
manifest: Manifest,
|
||||
removing: InstalledPackageDataEntry,
|
||||
},
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
Installed {
|
||||
static_files: StaticFiles,
|
||||
manifest: Manifest,
|
||||
installed: InstalledPackageDataEntry,
|
||||
},
|
||||
Installing(PackageDataEntryInstalling),
|
||||
Updating(PackageDataEntryUpdating),
|
||||
Restoring(PackageDataEntryRestoring),
|
||||
Removing(PackageDataEntryRemoving),
|
||||
Installed(PackageDataEntryInstalled),
|
||||
}
|
||||
impl PackageDataEntry {
|
||||
pub fn installed(&self) -> Option<&InstalledPackageDataEntry> {
|
||||
match self {
|
||||
Self::Installing { .. } | Self::Restoring { .. } | Self::Removing { .. } => None,
|
||||
Self::Updating { installed, .. } | Self::Installed { installed, .. } => Some(installed),
|
||||
impl Model<PackageDataEntry> {
|
||||
pub fn expect_into_installed(self) -> Result<Model<PackageDataEntryInstalled>, Error> {
|
||||
if let PackageDataEntryMatchModel::Installed(a) = self.into_match() {
|
||||
Ok(a)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("package is not in installed state"),
|
||||
ErrorKind::InvalidRequest,
|
||||
))
|
||||
}
|
||||
}
|
||||
pub fn installed_mut(&mut self) -> Option<&mut InstalledPackageDataEntry> {
|
||||
match self {
|
||||
Self::Installing { .. } | Self::Restoring { .. } | Self::Removing { .. } => None,
|
||||
Self::Updating { installed, .. } | Self::Installed { installed, .. } => Some(installed),
|
||||
pub fn expect_as_installed(&self) -> Result<&Model<PackageDataEntryInstalled>, Error> {
|
||||
if let PackageDataEntryMatchModelRef::Installed(a) = self.as_match() {
|
||||
Ok(a)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("package is not in installed state"),
|
||||
ErrorKind::InvalidRequest,
|
||||
))
|
||||
}
|
||||
}
|
||||
pub fn into_installed(self) -> Option<InstalledPackageDataEntry> {
|
||||
match self {
|
||||
Self::Installing { .. } | Self::Restoring { .. } | Self::Removing { .. } => None,
|
||||
Self::Updating { installed, .. } | Self::Installed { installed, .. } => Some(installed),
|
||||
pub fn expect_as_installed_mut(
|
||||
&mut self,
|
||||
) -> Result<&mut Model<PackageDataEntryInstalled>, Error> {
|
||||
if let PackageDataEntryMatchModelMut::Installed(a) = self.as_match_mut() {
|
||||
Ok(a)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("package is not in installed state"),
|
||||
ErrorKind::InvalidRequest,
|
||||
))
|
||||
}
|
||||
}
|
||||
pub fn manifest(self) -> Manifest {
|
||||
match self {
|
||||
PackageDataEntry::Installing { manifest, .. } => manifest,
|
||||
PackageDataEntry::Updating { manifest, .. } => manifest,
|
||||
PackageDataEntry::Restoring { manifest, .. } => manifest,
|
||||
PackageDataEntry::Removing { manifest, .. } => manifest,
|
||||
PackageDataEntry::Installed { manifest, .. } => manifest,
|
||||
pub fn expect_into_removing(self) -> Result<Model<PackageDataEntryRemoving>, Error> {
|
||||
if let PackageDataEntryMatchModel::Removing(a) = self.into_match() {
|
||||
Ok(a)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("package is not in removing state"),
|
||||
ErrorKind::InvalidRequest,
|
||||
))
|
||||
}
|
||||
}
|
||||
pub fn manifest_borrow(&self) -> &Manifest {
|
||||
match self {
|
||||
PackageDataEntry::Installing { manifest, .. } => manifest,
|
||||
PackageDataEntry::Updating { manifest, .. } => manifest,
|
||||
PackageDataEntry::Restoring { manifest, .. } => manifest,
|
||||
PackageDataEntry::Removing { manifest, .. } => manifest,
|
||||
PackageDataEntry::Installed { manifest, .. } => manifest,
|
||||
pub fn expect_as_removing(&self) -> Result<&Model<PackageDataEntryRemoving>, Error> {
|
||||
if let PackageDataEntryMatchModelRef::Removing(a) = self.as_match() {
|
||||
Ok(a)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("package is not in removing state"),
|
||||
ErrorKind::InvalidRequest,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PackageDataEntryModel {
|
||||
pub fn installed(self) -> OptionModel<InstalledPackageDataEntry> {
|
||||
self.0.child("installed").into()
|
||||
pub fn expect_as_removing_mut(
|
||||
&mut self,
|
||||
) -> Result<&mut Model<PackageDataEntryRemoving>, Error> {
|
||||
if let PackageDataEntryMatchModelMut::Removing(a) = self.as_match_mut() {
|
||||
Ok(a)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("package is not in removing state"),
|
||||
ErrorKind::InvalidRequest,
|
||||
))
|
||||
}
|
||||
}
|
||||
pub fn removing(self) -> OptionModel<InstalledPackageDataEntry> {
|
||||
self.0.child("removing").into()
|
||||
pub fn expect_as_installing_mut(
|
||||
&mut self,
|
||||
) -> Result<&mut Model<PackageDataEntryInstalling>, Error> {
|
||||
if let PackageDataEntryMatchModelMut::Installing(a) = self.as_match_mut() {
|
||||
Ok(a)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
eyre!("package is not in installing state"),
|
||||
ErrorKind::InvalidRequest,
|
||||
))
|
||||
}
|
||||
}
|
||||
pub fn install_progress(self) -> OptionModel<InstallProgress> {
|
||||
self.0.child("install-progress").into()
|
||||
pub fn into_manifest(self) -> Model<Manifest> {
|
||||
match self.into_match() {
|
||||
PackageDataEntryMatchModel::Installing(a) => a.into_manifest(),
|
||||
PackageDataEntryMatchModel::Updating(a) => a.into_installed().into_manifest(),
|
||||
PackageDataEntryMatchModel::Restoring(a) => a.into_manifest(),
|
||||
PackageDataEntryMatchModel::Removing(a) => a.into_manifest(),
|
||||
PackageDataEntryMatchModel::Installed(a) => a.into_manifest(),
|
||||
PackageDataEntryMatchModel::Error(_) => Model::from(Value::Null),
|
||||
}
|
||||
}
|
||||
pub fn manifest(self) -> ManifestModel {
|
||||
self.0.child("manifest").into()
|
||||
pub fn as_manifest(&self) -> &Model<Manifest> {
|
||||
match self.as_match() {
|
||||
PackageDataEntryMatchModelRef::Installing(a) => a.as_manifest(),
|
||||
PackageDataEntryMatchModelRef::Updating(a) => a.as_installed().as_manifest(),
|
||||
PackageDataEntryMatchModelRef::Restoring(a) => a.as_manifest(),
|
||||
PackageDataEntryMatchModelRef::Removing(a) => a.as_manifest(),
|
||||
PackageDataEntryMatchModelRef::Installed(a) => a.as_manifest(),
|
||||
PackageDataEntryMatchModelRef::Error(_) => (&Value::Null).into(),
|
||||
}
|
||||
}
|
||||
pub fn into_installed(self) -> Option<Model<InstalledPackageInfo>> {
|
||||
match self.into_match() {
|
||||
PackageDataEntryMatchModel::Installing(_) => None,
|
||||
PackageDataEntryMatchModel::Updating(a) => Some(a.into_installed()),
|
||||
PackageDataEntryMatchModel::Restoring(_) => None,
|
||||
PackageDataEntryMatchModel::Removing(_) => None,
|
||||
PackageDataEntryMatchModel::Installed(a) => Some(a.into_installed()),
|
||||
PackageDataEntryMatchModel::Error(_) => None,
|
||||
}
|
||||
}
|
||||
pub fn as_installed(&self) -> Option<&Model<InstalledPackageInfo>> {
|
||||
match self.as_match() {
|
||||
PackageDataEntryMatchModelRef::Installing(_) => None,
|
||||
PackageDataEntryMatchModelRef::Updating(a) => Some(a.as_installed()),
|
||||
PackageDataEntryMatchModelRef::Restoring(_) => None,
|
||||
PackageDataEntryMatchModelRef::Removing(_) => None,
|
||||
PackageDataEntryMatchModelRef::Installed(a) => Some(a.as_installed()),
|
||||
PackageDataEntryMatchModelRef::Error(_) => None,
|
||||
}
|
||||
}
|
||||
pub fn as_installed_mut(&mut self) -> Option<&mut Model<InstalledPackageInfo>> {
|
||||
match self.as_match_mut() {
|
||||
PackageDataEntryMatchModelMut::Installing(_) => None,
|
||||
PackageDataEntryMatchModelMut::Updating(a) => Some(a.as_installed_mut()),
|
||||
PackageDataEntryMatchModelMut::Restoring(_) => None,
|
||||
PackageDataEntryMatchModelMut::Removing(_) => None,
|
||||
PackageDataEntryMatchModelMut::Installed(a) => Some(a.as_installed_mut()),
|
||||
PackageDataEntryMatchModelMut::Error(_) => None,
|
||||
}
|
||||
}
|
||||
pub fn as_install_progress(&self) -> Option<&Model<Arc<InstallProgress>>> {
|
||||
match self.as_match() {
|
||||
PackageDataEntryMatchModelRef::Installing(a) => Some(a.as_install_progress()),
|
||||
PackageDataEntryMatchModelRef::Updating(a) => Some(a.as_install_progress()),
|
||||
PackageDataEntryMatchModelRef::Restoring(a) => Some(a.as_install_progress()),
|
||||
PackageDataEntryMatchModelRef::Removing(_) => None,
|
||||
PackageDataEntryMatchModelRef::Installed(_) => None,
|
||||
PackageDataEntryMatchModelRef::Error(_) => None,
|
||||
}
|
||||
}
|
||||
pub fn as_install_progress_mut(&mut self) -> Option<&mut Model<Arc<InstallProgress>>> {
|
||||
match self.as_match_mut() {
|
||||
PackageDataEntryMatchModelMut::Installing(a) => Some(a.as_install_progress_mut()),
|
||||
PackageDataEntryMatchModelMut::Updating(a) => Some(a.as_install_progress_mut()),
|
||||
PackageDataEntryMatchModelMut::Restoring(a) => Some(a.as_install_progress_mut()),
|
||||
PackageDataEntryMatchModelMut::Removing(_) => None,
|
||||
PackageDataEntryMatchModelMut::Installed(_) => None,
|
||||
PackageDataEntryMatchModelMut::Error(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct InstalledPackageDataEntry {
|
||||
#[model]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct InstalledPackageInfo {
|
||||
pub status: Status,
|
||||
pub marketplace_url: Option<Url>,
|
||||
#[serde(default)]
|
||||
#[serde(with = "crate::util::serde::ed25519_pubkey")]
|
||||
pub developer_key: ed25519_dalek::PublicKey,
|
||||
#[model]
|
||||
pub manifest: Manifest,
|
||||
pub last_backup: Option<DateTime<Utc>>,
|
||||
#[model]
|
||||
pub system_pointers: Vec<SystemPointerSpec>,
|
||||
#[model]
|
||||
pub dependency_info: BTreeMap<PackageId, StaticDependencyInfo>,
|
||||
#[model]
|
||||
pub current_dependents: CurrentDependents,
|
||||
#[model]
|
||||
pub current_dependencies: CurrentDependencies,
|
||||
#[model]
|
||||
pub interface_addresses: InterfaceAddressMap,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct CurrentDependents(pub BTreeMap<PackageId, CurrentDependencyInfo>);
|
||||
impl CurrentDependents {
|
||||
pub fn map(
|
||||
@@ -348,12 +448,6 @@ impl CurrentDependents {
|
||||
impl Map for CurrentDependents {
|
||||
type Key = PackageId;
|
||||
type Value = CurrentDependencyInfo;
|
||||
fn get(&self, key: &Self::Key) -> Option<&Self::Value> {
|
||||
self.0.get(key)
|
||||
}
|
||||
}
|
||||
impl HasModel for CurrentDependents {
|
||||
type Model = MapModel<Self>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
@@ -372,25 +466,21 @@ impl CurrentDependencies {
|
||||
impl Map for CurrentDependencies {
|
||||
type Key = PackageId;
|
||||
type Value = CurrentDependencyInfo;
|
||||
fn get(&self, key: &Self::Key) -> Option<&Self::Value> {
|
||||
self.0.get(key)
|
||||
}
|
||||
}
|
||||
impl HasModel for CurrentDependencies {
|
||||
type Model = MapModel<Self>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct StaticDependencyInfo {
|
||||
pub manifest: Option<Manifest>,
|
||||
pub icon: String,
|
||||
pub title: String,
|
||||
pub icon: DataUrl<'static>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct CurrentDependencyInfo {
|
||||
pub pointers: Vec<PackagePointerSpec>,
|
||||
pub pointers: BTreeSet<PackagePointerSpec>,
|
||||
pub health_checks: BTreeSet<HealthCheckId>,
|
||||
}
|
||||
|
||||
@@ -399,27 +489,12 @@ pub struct InterfaceAddressMap(pub BTreeMap<InterfaceId, InterfaceAddresses>);
|
||||
impl Map for InterfaceAddressMap {
|
||||
type Key = InterfaceId;
|
||||
type Value = InterfaceAddresses;
|
||||
fn get(&self, key: &Self::Key) -> Option<&Self::Value> {
|
||||
self.0.get(key)
|
||||
}
|
||||
}
|
||||
impl HasModel for InterfaceAddressMap {
|
||||
type Model = MapModel<Self>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct InterfaceAddresses {
|
||||
#[model]
|
||||
pub tor_address: Option<String>,
|
||||
#[model]
|
||||
pub lan_address: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct RecoveredPackageInfo {
|
||||
pub title: String,
|
||||
pub icon: String,
|
||||
pub version: Version,
|
||||
}
|
||||
|
||||
@@ -1,75 +1,22 @@
|
||||
use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, Verifier};
|
||||
use models::Version;
|
||||
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::Error;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
|
||||
pub struct PackageReceipts {
|
||||
package_data: LockReceipt<super::model::AllPackageData, ()>,
|
||||
}
|
||||
|
||||
impl PackageReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||
let package_data = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
package_data: package_data.verify(&skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_packages<Db: DbHandle>(
|
||||
db: &mut Db,
|
||||
receipts: &PackageReceipts,
|
||||
) -> Result<Vec<PackageId>, Error> {
|
||||
let packages = receipts.package_data.get(db).await?;
|
||||
Ok(packages.0.keys().cloned().collect())
|
||||
}
|
||||
|
||||
pub struct ManifestReceipts {
|
||||
manifest: LockReceipt<Manifest, String>,
|
||||
}
|
||||
|
||||
impl ManifestReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks, id);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<LockTargetId>,
|
||||
_id: &PackageId,
|
||||
) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||
let manifest = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.manifest()
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
manifest: manifest.verify(&skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_manifest<Db: DbHandle>(
|
||||
db: &mut Db,
|
||||
pkg: &PackageId,
|
||||
receipts: &ManifestReceipts,
|
||||
) -> Result<Option<Manifest>, Error> {
|
||||
Ok(receipts.manifest.get(db, pkg).await?)
|
||||
pub fn get_packages(db: Peeked) -> Result<Vec<(PackageId, Version)>, Error> {
|
||||
Ok(db
|
||||
.as_package_data()
|
||||
.keys()?
|
||||
.into_iter()
|
||||
.flat_map(|package_id| {
|
||||
let version = db
|
||||
.as_package_data()
|
||||
.as_idx(&package_id)?
|
||||
.as_manifest()
|
||||
.as_version()
|
||||
.de()
|
||||
.ok()?;
|
||||
Some((package_id, version))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
382
backend/src/db/prelude.rs
Normal file
382
backend/src/db/prelude.rs
Normal file
@@ -0,0 +1,382 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::marker::PhantomData;
|
||||
use std::panic::UnwindSafe;
|
||||
|
||||
use patch_db::value::InternedString;
|
||||
pub use patch_db::{HasModel, PatchDb, Value};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::db::model::DatabaseModel;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub type Peeked = Model<super::model::Database>;
|
||||
|
||||
pub fn to_value<T>(value: &T) -> Result<Value, Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
patch_db::value::to_value(value).with_kind(ErrorKind::Serialization)
|
||||
}
|
||||
|
||||
pub fn from_value<T>(value: Value) -> Result<T, Error>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
patch_db::value::from_value(value).with_kind(ErrorKind::Deserialization)
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait PatchDbExt {
|
||||
async fn peek(&self) -> Result<DatabaseModel, Error>;
|
||||
async fn mutate<U: UnwindSafe + Send>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut DatabaseModel) -> Result<U, Error> + UnwindSafe + Send,
|
||||
) -> Result<U, Error>;
|
||||
async fn map_mutate(
|
||||
&self,
|
||||
f: impl FnOnce(DatabaseModel) -> Result<DatabaseModel, Error> + UnwindSafe + Send,
|
||||
) -> Result<DatabaseModel, Error>;
|
||||
}
|
||||
#[async_trait::async_trait]
|
||||
impl PatchDbExt for PatchDb {
|
||||
async fn peek(&self) -> Result<DatabaseModel, Error> {
|
||||
Ok(DatabaseModel::from(self.dump().await?.value))
|
||||
}
|
||||
async fn mutate<U: UnwindSafe + Send>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut DatabaseModel) -> Result<U, Error> + UnwindSafe + Send,
|
||||
) -> Result<U, Error> {
|
||||
Ok(self
|
||||
.apply_function(|mut v| {
|
||||
let model = <&mut DatabaseModel>::from(&mut v);
|
||||
let res = f(model)?;
|
||||
Ok::<_, Error>((v, res))
|
||||
})
|
||||
.await?
|
||||
.1)
|
||||
}
|
||||
async fn map_mutate(
|
||||
&self,
|
||||
f: impl FnOnce(DatabaseModel) -> Result<DatabaseModel, Error> + UnwindSafe + Send,
|
||||
) -> Result<DatabaseModel, Error> {
|
||||
Ok(DatabaseModel::from(
|
||||
self.apply_function(|v| f(DatabaseModel::from(v)).map(|a| (a.into(), ())))
|
||||
.await?
|
||||
.0,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// &mut Model<T> <=> &mut Value
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug)]
|
||||
pub struct Model<T> {
|
||||
value: Value,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
impl<T: DeserializeOwned> Model<T> {
|
||||
pub fn de(&self) -> Result<T, Error> {
|
||||
from_value(self.value.clone())
|
||||
}
|
||||
}
|
||||
impl<T: Serialize> Model<T> {
|
||||
pub fn new(value: &T) -> Result<Self, Error> {
|
||||
Ok(Self::from(to_value(value)?))
|
||||
}
|
||||
pub fn ser(&mut self, value: &T) -> Result<(), Error> {
|
||||
self.value = to_value(value)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize + DeserializeOwned> Model<T> {
|
||||
pub fn replace(&mut self, value: &T) -> Result<T, Error> {
|
||||
let orig = self.de()?;
|
||||
self.ser(value)?;
|
||||
Ok(orig)
|
||||
}
|
||||
}
|
||||
impl<T> Clone for Model<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
value: self.value.clone(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> From<Value> for Model<T> {
|
||||
fn from(value: Value) -> Self {
|
||||
Self {
|
||||
value,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> From<Model<T>> for Value {
|
||||
fn from(value: Model<T>) -> Self {
|
||||
value.value
|
||||
}
|
||||
}
|
||||
impl<'a, T> From<&'a Value> for &'a Model<T> {
|
||||
fn from(value: &'a Value) -> Self {
|
||||
unsafe { std::mem::transmute(value) }
|
||||
}
|
||||
}
|
||||
impl<'a, T> From<&'a Model<T>> for &'a Value {
|
||||
fn from(value: &'a Model<T>) -> Self {
|
||||
unsafe { std::mem::transmute(value) }
|
||||
}
|
||||
}
|
||||
impl<'a, T> From<&'a mut Value> for &mut Model<T> {
|
||||
fn from(value: &'a mut Value) -> Self {
|
||||
unsafe { std::mem::transmute(value) }
|
||||
}
|
||||
}
|
||||
impl<'a, T> From<&'a mut Model<T>> for &mut Value {
|
||||
fn from(value: &'a mut Model<T>) -> Self {
|
||||
unsafe { std::mem::transmute(value) }
|
||||
}
|
||||
}
|
||||
impl<T> patch_db::Model<T> for Model<T> {
|
||||
type Model<U> = Model<U>;
|
||||
}
|
||||
|
||||
impl<T> Model<Option<T>> {
|
||||
pub fn transpose(self) -> Option<Model<T>> {
|
||||
use patch_db::ModelExt;
|
||||
if self.value.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(self.transmute(|a| a))
|
||||
}
|
||||
}
|
||||
pub fn transpose_ref(&self) -> Option<&Model<T>> {
|
||||
use patch_db::ModelExt;
|
||||
if self.value.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(self.transmute_ref(|a| a))
|
||||
}
|
||||
}
|
||||
pub fn transpose_mut(&mut self) -> Option<&mut Model<T>> {
|
||||
use patch_db::ModelExt;
|
||||
if self.value.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(self.transmute_mut(|a| a))
|
||||
}
|
||||
}
|
||||
pub fn from_option(opt: Option<Model<T>>) -> Self {
|
||||
use patch_db::ModelExt;
|
||||
match opt {
|
||||
Some(a) => a.transmute(|a| a),
|
||||
None => Self::from_value(Value::Null),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Map: DeserializeOwned + Serialize {
|
||||
type Key;
|
||||
type Value;
|
||||
}
|
||||
|
||||
impl<A, B> Map for BTreeMap<A, B>
|
||||
where
|
||||
A: serde::Serialize + serde::de::DeserializeOwned + Ord,
|
||||
B: serde::Serialize + serde::de::DeserializeOwned,
|
||||
{
|
||||
type Key = A;
|
||||
type Value = B;
|
||||
}
|
||||
|
||||
impl<T: Map> Model<T>
|
||||
where
|
||||
T::Key: AsRef<str>,
|
||||
T::Value: Serialize,
|
||||
{
|
||||
pub fn insert(&mut self, key: &T::Key, value: &T::Value) -> Result<(), Error> {
|
||||
use serde::ser::Error;
|
||||
let v = patch_db::value::to_value(value)?;
|
||||
match &mut self.value {
|
||||
Value::Object(o) => {
|
||||
o.insert(InternedString::intern(key.as_ref()), v);
|
||||
Ok(())
|
||||
}
|
||||
v => Err(patch_db::value::Error {
|
||||
source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")),
|
||||
kind: patch_db::value::ErrorKind::Serialization,
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
pub fn insert_model(&mut self, key: &T::Key, value: Model<T::Value>) -> Result<(), Error> {
|
||||
use patch_db::ModelExt;
|
||||
use serde::ser::Error;
|
||||
let v = value.into_value();
|
||||
match &mut self.value {
|
||||
Value::Object(o) => {
|
||||
o.insert(InternedString::intern(key.as_ref()), v);
|
||||
Ok(())
|
||||
}
|
||||
v => Err(patch_db::value::Error {
|
||||
source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")),
|
||||
kind: patch_db::value::ErrorKind::Serialization,
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Map> Model<T>
|
||||
where
|
||||
T::Key: DeserializeOwned + Ord + Clone,
|
||||
{
|
||||
pub fn keys(&self) -> Result<Vec<T::Key>, Error> {
|
||||
use serde::de::Error;
|
||||
use serde::Deserialize;
|
||||
match &self.value {
|
||||
Value::Object(o) => o
|
||||
.keys()
|
||||
.cloned()
|
||||
.map(|k| {
|
||||
T::Key::deserialize(patch_db::value::de::InternedStringDeserializer::from(k))
|
||||
.map_err(|e| {
|
||||
patch_db::value::Error {
|
||||
kind: patch_db::value::ErrorKind::Deserialization,
|
||||
source: e,
|
||||
}
|
||||
.into()
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
v => Err(patch_db::value::Error {
|
||||
source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")),
|
||||
kind: patch_db::value::ErrorKind::Deserialization,
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_entries(self) -> Result<Vec<(T::Key, Model<T::Value>)>, Error> {
|
||||
use patch_db::ModelExt;
|
||||
use serde::de::Error;
|
||||
use serde::Deserialize;
|
||||
match self.value {
|
||||
Value::Object(o) => o
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
Ok((
|
||||
T::Key::deserialize(patch_db::value::de::InternedStringDeserializer::from(
|
||||
k,
|
||||
))
|
||||
.with_kind(ErrorKind::Deserialization)?,
|
||||
Model::from_value(v),
|
||||
))
|
||||
})
|
||||
.collect(),
|
||||
v => Err(patch_db::value::Error {
|
||||
source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")),
|
||||
kind: patch_db::value::ErrorKind::Deserialization,
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
pub fn as_entries(&self) -> Result<Vec<(T::Key, &Model<T::Value>)>, Error> {
|
||||
use patch_db::ModelExt;
|
||||
use serde::de::Error;
|
||||
use serde::Deserialize;
|
||||
match &self.value {
|
||||
Value::Object(o) => o
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
Ok((
|
||||
T::Key::deserialize(patch_db::value::de::InternedStringDeserializer::from(
|
||||
k.clone(),
|
||||
))
|
||||
.with_kind(ErrorKind::Deserialization)?,
|
||||
Model::value_as(v),
|
||||
))
|
||||
})
|
||||
.collect(),
|
||||
v => Err(patch_db::value::Error {
|
||||
source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")),
|
||||
kind: patch_db::value::ErrorKind::Deserialization,
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
pub fn as_entries_mut(&mut self) -> Result<Vec<(T::Key, &mut Model<T::Value>)>, Error> {
|
||||
use patch_db::ModelExt;
|
||||
use serde::de::Error;
|
||||
use serde::Deserialize;
|
||||
match &mut self.value {
|
||||
Value::Object(o) => o
|
||||
.iter_mut()
|
||||
.map(|(k, v)| {
|
||||
Ok((
|
||||
T::Key::deserialize(patch_db::value::de::InternedStringDeserializer::from(
|
||||
k.clone(),
|
||||
))
|
||||
.with_kind(ErrorKind::Deserialization)?,
|
||||
Model::value_as_mut(v),
|
||||
))
|
||||
})
|
||||
.collect(),
|
||||
v => Err(patch_db::value::Error {
|
||||
source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")),
|
||||
kind: patch_db::value::ErrorKind::Deserialization,
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Map> Model<T>
|
||||
where
|
||||
T::Key: AsRef<str>,
|
||||
{
|
||||
pub fn into_idx(self, key: &T::Key) -> Option<Model<T::Value>> {
|
||||
use patch_db::ModelExt;
|
||||
match &self.value {
|
||||
Value::Object(o) if o.contains_key(key.as_ref()) => Some(self.transmute(|v| {
|
||||
use patch_db::value::index::Index;
|
||||
key.as_ref().index_into_owned(v).unwrap()
|
||||
})),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn as_idx<'a>(&'a self, key: &T::Key) -> Option<&'a Model<T::Value>> {
|
||||
use patch_db::ModelExt;
|
||||
match &self.value {
|
||||
Value::Object(o) if o.contains_key(key.as_ref()) => Some(self.transmute_ref(|v| {
|
||||
use patch_db::value::index::Index;
|
||||
key.as_ref().index_into(v).unwrap()
|
||||
})),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn as_idx_mut<'a>(&'a mut self, key: &T::Key) -> Option<&'a mut Model<T::Value>> {
|
||||
use patch_db::ModelExt;
|
||||
match &mut self.value {
|
||||
Value::Object(o) if o.contains_key(key.as_ref()) => Some(self.transmute_mut(|v| {
|
||||
use patch_db::value::index::Index;
|
||||
key.as_ref().index_or_insert(v)
|
||||
})),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn remove(&mut self, key: &T::Key) -> Result<Option<Model<T::Value>>, Error> {
|
||||
use serde::ser::Error;
|
||||
match &mut self.value {
|
||||
Value::Object(o) => {
|
||||
let v = o.remove(key.as_ref());
|
||||
Ok(v.map(patch_db::ModelExt::from_value))
|
||||
}
|
||||
v => Err(patch_db::value::Error {
|
||||
source: patch_db::value::ErrorSource::custom(format!("expected object found {v}")),
|
||||
kind: patch_db::value::ErrorKind::Serialization,
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
use color_eyre::eyre::eyre;
|
||||
pub use models::{Error, ErrorKind, ResultExt};
|
||||
pub use models::{Error, ErrorKind, OptionExt, ResultExt};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ErrorCollection(Vec<Error>);
|
||||
@@ -54,7 +54,7 @@ impl std::fmt::Display for ErrorCollection {
|
||||
macro_rules! ensure_code {
|
||||
($x:expr, $c:expr, $fmt:expr $(, $arg:expr)*) => {
|
||||
if !($x) {
|
||||
return Err(crate::Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c));
|
||||
return Err(crate::error::Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,6 +60,14 @@ pub async fn set_hostname(hostname: &Hostname) -> Result<(), Error> {
|
||||
.arg(hostname)
|
||||
.invoke(ErrorKind::ParseSysInfo)
|
||||
.await?;
|
||||
Command::new("sed")
|
||||
.arg("-i")
|
||||
.arg(format!(
|
||||
"s/\\(\\s\\)localhost\\( {hostname}\\)\\?/\\1localhost {hostname}/g"
|
||||
))
|
||||
.arg("/etc/hosts")
|
||||
.invoke(ErrorKind::ParseSysInfo)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::Permissions;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
@@ -7,17 +6,18 @@ use std::time::Duration;
|
||||
use color_eyre::eyre::eyre;
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use models::ResultExt;
|
||||
use patch_db::{DbHandle, LockReceipt, LockType};
|
||||
use rand::random;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::account::AccountInfo;
|
||||
use crate::context::rpc::RpcContextConfig;
|
||||
use crate::db::model::{ServerInfo, ServerStatus};
|
||||
use crate::db::model::ServerStatus;
|
||||
use crate::disk::mount::util::unmount;
|
||||
use crate::install::PKG_ARCHIVE_DIR;
|
||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
||||
use crate::prelude::*;
|
||||
use crate::sound::BEP;
|
||||
use crate::system::time;
|
||||
use crate::util::docker::{create_bridge_network, CONTAINER_DATADIR, CONTAINER_TOOL};
|
||||
@@ -40,40 +40,8 @@ pub async fn check_time_is_synchronized() -> Result<bool, Error> {
|
||||
== "NTPSynchronized=yes")
|
||||
}
|
||||
|
||||
pub struct InitReceipts {
|
||||
pub server_info: LockReceipt<ServerInfo, ()>,
|
||||
pub server_version: LockReceipt<crate::util::Version, ()>,
|
||||
pub version_range: LockReceipt<emver::VersionRange, ()>,
|
||||
}
|
||||
impl InitReceipts {
|
||||
pub async fn new(db: &mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let server_info = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(&mut locks);
|
||||
let server_version = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.version()
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(&mut locks);
|
||||
let version_range = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.eos_version_compat()
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(&mut locks);
|
||||
|
||||
let skeleton_key = db.lock_all(locks).await?;
|
||||
Ok(Self {
|
||||
server_info: server_info.verify(&skeleton_key)?,
|
||||
server_version: server_version.verify(&skeleton_key)?,
|
||||
version_range: version_range.verify(&skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// must be idempotent
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn init_postgres(datadir: impl AsRef<Path>) -> Result<(), Error> {
|
||||
let db_dir = datadir.as_ref().join("main/postgresql");
|
||||
if tokio::process::Command::new("mountpoint")
|
||||
@@ -134,7 +102,11 @@ pub async fn init_postgres(datadir: impl AsRef<Path>) -> Result<(), Error> {
|
||||
tmp
|
||||
};
|
||||
if tokio::fs::metadata(&conf_dir).await.is_ok() {
|
||||
tokio::fs::rename(&conf_dir, &conf_dir_tmp).await?;
|
||||
Command::new("mv")
|
||||
.arg(&conf_dir)
|
||||
.arg(&conf_dir_tmp)
|
||||
.invoke(ErrorKind::Filesystem)
|
||||
.await?;
|
||||
}
|
||||
let mut old_version = pg_version;
|
||||
while old_version > 13
|
||||
@@ -155,7 +127,11 @@ pub async fn init_postgres(datadir: impl AsRef<Path>) -> Result<(), Error> {
|
||||
if tokio::fs::metadata(&conf_dir).await.is_ok() {
|
||||
tokio::fs::remove_dir_all(&conf_dir).await?;
|
||||
}
|
||||
tokio::fs::rename(&conf_dir_tmp, &conf_dir).await?;
|
||||
Command::new("mv")
|
||||
.arg(&conf_dir_tmp)
|
||||
.arg(&conf_dir)
|
||||
.invoke(ErrorKind::Filesystem)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +167,7 @@ pub struct InitResult {
|
||||
pub db: patch_db::PatchDb,
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
tokio::fs::create_dir_all("/run/embassy")
|
||||
.await
|
||||
@@ -223,13 +200,14 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
|
||||
let account = AccountInfo::load(&secret_store).await?;
|
||||
let db = cfg.db(&account).await?;
|
||||
db.mutate(|d| {
|
||||
let model = d.de()?;
|
||||
d.ser(&model)
|
||||
})
|
||||
.await?;
|
||||
tracing::info!("Opened PatchDB");
|
||||
let mut handle = db.handle();
|
||||
let mut server_info = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.get_mut(&mut handle)
|
||||
.await?;
|
||||
let receipts = InitReceipts::new(&mut handle).await?;
|
||||
let peek = db.peek().await?;
|
||||
let mut server_info = peek.as_server_info().de()?;
|
||||
|
||||
// write to ca cert store
|
||||
tokio::fs::write(
|
||||
@@ -337,6 +315,7 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
}
|
||||
|
||||
if CONTAINER_TOOL == "podman" {
|
||||
crate::util::docker::remove_container("netdummy", true).await?;
|
||||
Command::new("podman")
|
||||
.arg("run")
|
||||
.arg("-d")
|
||||
@@ -388,9 +367,13 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
||||
|
||||
server_info.system_start_time = time().await?;
|
||||
|
||||
server_info.save(&mut handle).await?;
|
||||
db.mutate(|v| {
|
||||
v.as_server_info_mut().ser(&server_info)?;
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
crate::version::init(&mut handle, &secret_store, &receipts).await?;
|
||||
crate::version::init(&db, &secret_store).await?;
|
||||
|
||||
if should_rebuild {
|
||||
match tokio::fs::remove_file(SYSTEM_REBUILD_PATH).await {
|
||||
|
||||
@@ -1,102 +1,23 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, PatchDbHandle, Verifier};
|
||||
use models::OptionExt;
|
||||
use sqlx::{Executor, Postgres};
|
||||
use tracing::instrument;
|
||||
|
||||
use super::PKG_ARCHIVE_DIR;
|
||||
use crate::config::{not_found, ConfigReceipts};
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::{
|
||||
AllPackageData, CurrentDependencies, CurrentDependents, InstalledPackageDataEntry,
|
||||
PackageDataEntry,
|
||||
};
|
||||
use crate::dependencies::{
|
||||
reconfigure_dependents_with_live_pointers, DependencyErrors, TryHealReceipts,
|
||||
CurrentDependencies, Database, PackageDataEntry, PackageDataEntryInstalled,
|
||||
PackageDataEntryMatchModelRef,
|
||||
};
|
||||
use crate::error::ErrorCollection;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::{Apply, Version};
|
||||
use crate::volume::{asset_dir, script_dir};
|
||||
use crate::Error;
|
||||
|
||||
pub struct UpdateDependencyReceipts {
|
||||
try_heal: TryHealReceipts,
|
||||
dependency_errors: LockReceipt<DependencyErrors, String>,
|
||||
manifest: LockReceipt<Manifest, String>,
|
||||
}
|
||||
impl UpdateDependencyReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||
let dependency_errors = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.status().dependency_errors())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let manifest = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let try_heal = TryHealReceipts::setup(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
dependency_errors: dependency_errors.verify(skeleton_key)?,
|
||||
manifest: manifest.verify(skeleton_key)?,
|
||||
try_heal: try_heal(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn update_dependency_errors_of_dependents<'a, Db: DbHandle>(
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
id: &PackageId,
|
||||
deps: &CurrentDependents,
|
||||
receipts: &UpdateDependencyReceipts,
|
||||
) -> Result<(), Error> {
|
||||
for dep in deps.0.keys() {
|
||||
if let Some(man) = receipts.manifest.get(db, dep).await? {
|
||||
if let Err(e) = if let Some(info) = man.dependencies.0.get(id) {
|
||||
info.satisfied(ctx, db, id, None, dep, &receipts.try_heal)
|
||||
.await?
|
||||
} else {
|
||||
Ok(())
|
||||
} {
|
||||
let mut errs = receipts
|
||||
.dependency_errors
|
||||
.get(db, dep)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(dep))?;
|
||||
errs.0.insert(id.clone(), e);
|
||||
receipts.dependency_errors.set(db, errs, dep).await?
|
||||
} else {
|
||||
let mut errs = receipts
|
||||
.dependency_errors
|
||||
.get(db, dep)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(dep))?;
|
||||
errs.0.remove(id);
|
||||
receipts.dependency_errors.set(db, errs, dep).await?
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn cleanup(ctx: &RpcContext, id: &PackageId, version: &Version) -> Result<(), Error> {
|
||||
let mut errors = ErrorCollection::new();
|
||||
@@ -136,66 +57,26 @@ pub async fn cleanup(ctx: &RpcContext, id: &PackageId, version: &Version) -> Res
|
||||
errors.into_result()
|
||||
}
|
||||
|
||||
pub struct CleanupFailedReceipts {
|
||||
package_data_entry: LockReceipt<PackageDataEntry, String>,
|
||||
package_entries: LockReceipt<AllPackageData, ()>,
|
||||
}
|
||||
|
||||
impl CleanupFailedReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||
let package_data_entry = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let package_entries = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
package_data_entry: package_data_entry.verify(skeleton_key).unwrap(),
|
||||
package_entries: package_entries.verify(skeleton_key).unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn cleanup_failed<Db: DbHandle>(
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
id: &PackageId,
|
||||
receipts: &CleanupFailedReceipts,
|
||||
) -> Result<(), Error> {
|
||||
let pde = receipts
|
||||
.package_data_entry
|
||||
.get(db, id)
|
||||
pub async fn cleanup_failed(ctx: &RpcContext, id: &PackageId) -> Result<(), Error> {
|
||||
if let Some(version) = match ctx
|
||||
.db
|
||||
.peek()
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(id))?;
|
||||
if let Some(manifest) = match &pde {
|
||||
PackageDataEntry::Installing { manifest, .. }
|
||||
| PackageDataEntry::Restoring { manifest, .. } => Some(manifest),
|
||||
PackageDataEntry::Updating {
|
||||
manifest,
|
||||
installed:
|
||||
InstalledPackageDataEntry {
|
||||
manifest: installed_manifest,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
if &manifest.version != &installed_manifest.version {
|
||||
Some(manifest)
|
||||
.as_package_data()
|
||||
.as_idx(id)
|
||||
.or_not_found(id)?
|
||||
.as_match()
|
||||
{
|
||||
PackageDataEntryMatchModelRef::Installing(m) => Some(m.as_manifest().as_version().de()?),
|
||||
PackageDataEntryMatchModelRef::Restoring(m) => Some(m.as_manifest().as_version().de()?),
|
||||
PackageDataEntryMatchModelRef::Updating(m) => {
|
||||
let manifest_version = m.as_manifest().as_version().de()?;
|
||||
let installed = m.as_installed().as_manifest().as_version().de()?;
|
||||
if manifest_version != installed {
|
||||
Some(manifest_version)
|
||||
} else {
|
||||
None
|
||||
None // do not remove existing data
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -203,169 +84,107 @@ pub async fn cleanup_failed<Db: DbHandle>(
|
||||
None
|
||||
}
|
||||
} {
|
||||
cleanup(ctx, id, &manifest.version).await?;
|
||||
cleanup(ctx, id, &version).await?;
|
||||
}
|
||||
|
||||
match pde {
|
||||
PackageDataEntry::Installing { .. } | PackageDataEntry::Restoring { .. } => {
|
||||
let mut entries = receipts.package_entries.get(db).await?;
|
||||
entries.0.remove(id);
|
||||
receipts.package_entries.set(db, entries).await?;
|
||||
}
|
||||
PackageDataEntry::Updating {
|
||||
installed,
|
||||
static_files,
|
||||
..
|
||||
} => {
|
||||
receipts
|
||||
.package_data_entry
|
||||
.set(
|
||||
db,
|
||||
PackageDataEntry::Installed {
|
||||
manifest: installed.manifest.clone(),
|
||||
installed,
|
||||
static_files,
|
||||
},
|
||||
id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
ctx.db
|
||||
.mutate(|v| {
|
||||
match v
|
||||
.clone()
|
||||
.into_package_data()
|
||||
.into_idx(id)
|
||||
.or_not_found(id)?
|
||||
.as_match()
|
||||
{
|
||||
PackageDataEntryMatchModelRef::Installing(_)
|
||||
| PackageDataEntryMatchModelRef::Restoring(_) => {
|
||||
v.as_package_data_mut().remove(id)?;
|
||||
}
|
||||
PackageDataEntryMatchModelRef::Updating(pde) => {
|
||||
v.as_package_data_mut()
|
||||
.as_idx_mut(id)
|
||||
.or_not_found(id)?
|
||||
.ser(&PackageDataEntry::Installed(PackageDataEntryInstalled {
|
||||
manifest: pde.as_installed().as_manifest().de()?,
|
||||
static_files: pde.as_static_files().de()?,
|
||||
installed: pde.as_installed().de()?,
|
||||
}))?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn remove_from_current_dependents_lists<'a, Db: DbHandle>(
|
||||
db: &mut Db,
|
||||
id: &'a PackageId,
|
||||
current_dependencies: &'a CurrentDependencies,
|
||||
current_dependent_receipt: &LockReceipt<CurrentDependents, String>,
|
||||
pub fn remove_from_current_dependents_lists(
|
||||
db: &mut Model<Database>,
|
||||
id: &PackageId,
|
||||
current_dependencies: &CurrentDependencies,
|
||||
) -> Result<(), Error> {
|
||||
for dep in current_dependencies.0.keys().chain(std::iter::once(id)) {
|
||||
if let Some(mut current_dependents) = current_dependent_receipt.get(db, dep).await? {
|
||||
if current_dependents.0.remove(id).is_some() {
|
||||
current_dependent_receipt
|
||||
.set(db, current_dependents, dep)
|
||||
.await?;
|
||||
}
|
||||
if let Some(current_dependents) = db
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(dep)
|
||||
.and_then(|d| d.as_installed_mut())
|
||||
.map(|i| i.as_current_dependents_mut())
|
||||
{
|
||||
current_dependents.remove(id)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub struct UninstallReceipts {
|
||||
config: ConfigReceipts,
|
||||
removing: LockReceipt<InstalledPackageDataEntry, ()>,
|
||||
packages: LockReceipt<AllPackageData, ()>,
|
||||
current_dependents: LockReceipt<CurrentDependents, String>,
|
||||
update_depenency_receipts: UpdateDependencyReceipts,
|
||||
}
|
||||
impl UninstallReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks, id);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<LockTargetId>,
|
||||
id: &PackageId,
|
||||
) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||
let config = ConfigReceipts::setup(locks);
|
||||
let removing = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|pde| pde.removing())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
|
||||
let current_dependents = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.current_dependents())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let packages = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let update_depenency_receipts = UpdateDependencyReceipts::setup(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
config: config(skeleton_key)?,
|
||||
removing: removing.verify(skeleton_key)?,
|
||||
current_dependents: current_dependents.verify(skeleton_key)?,
|
||||
update_depenency_receipts: update_depenency_receipts(skeleton_key)?,
|
||||
packages: packages.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
pub async fn uninstall<Ex>(
|
||||
ctx: &RpcContext,
|
||||
db: &mut PatchDbHandle,
|
||||
secrets: &mut Ex,
|
||||
id: &PackageId,
|
||||
) -> Result<(), Error>
|
||||
pub async fn uninstall<Ex>(ctx: &RpcContext, secrets: &mut Ex, id: &PackageId) -> Result<(), Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: Executor<'a, Database = Postgres>,
|
||||
{
|
||||
let mut tx = db.begin().await?;
|
||||
crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&id)
|
||||
.lock(&mut tx, LockType::Write)
|
||||
.await?;
|
||||
let receipts = UninstallReceipts::new(&mut tx, id).await?;
|
||||
let entry = receipts.removing.get(&mut tx).await?;
|
||||
cleanup(ctx, &entry.manifest.id, &entry.manifest.version).await?;
|
||||
let db = ctx.db.peek().await?;
|
||||
let entry = db
|
||||
.as_package_data()
|
||||
.as_idx(id)
|
||||
.or_not_found(id)?
|
||||
.expect_as_removing()?;
|
||||
|
||||
let packages = {
|
||||
let mut packages = receipts.packages.get(&mut tx).await?;
|
||||
packages.0.remove(id);
|
||||
packages
|
||||
};
|
||||
let dependents_paths: Vec<PathBuf> = entry
|
||||
.current_dependents
|
||||
.0
|
||||
.keys()
|
||||
.flat_map(|x| packages.0.get(x))
|
||||
.flat_map(|x| x.manifest_borrow().volumes.values())
|
||||
.as_removing()
|
||||
.as_current_dependents()
|
||||
.keys()?
|
||||
.into_iter()
|
||||
.filter(|x| x != id)
|
||||
.flat_map(|x| db.as_package_data().as_idx(&x))
|
||||
.flat_map(|x| x.as_installed())
|
||||
.flat_map(|x| x.as_manifest().as_volumes().de())
|
||||
.flat_map(|x| x.values().cloned().collect::<Vec<_>>())
|
||||
.flat_map(|x| x.pointer_path(&ctx.datadir))
|
||||
.collect();
|
||||
receipts.packages.set(&mut tx, packages).await?;
|
||||
// once we have removed the package entry, we can change all the dependent pointers to null
|
||||
reconfigure_dependents_with_live_pointers(ctx, &mut tx, &receipts.config, &entry).await?;
|
||||
|
||||
remove_from_current_dependents_lists(
|
||||
&mut tx,
|
||||
&entry.manifest.id,
|
||||
&entry.current_dependencies,
|
||||
&receipts.current_dependents,
|
||||
)
|
||||
.await?;
|
||||
update_dependency_errors_of_dependents(
|
||||
ctx,
|
||||
&mut tx,
|
||||
&entry.manifest.id,
|
||||
&entry.current_dependents,
|
||||
&receipts.update_depenency_receipts,
|
||||
)
|
||||
.await?;
|
||||
let volumes = ctx
|
||||
let volume_dir = ctx
|
||||
.datadir
|
||||
.join(crate::volume::PKG_VOLUME_DIR)
|
||||
.join(&entry.manifest.id);
|
||||
.join(&*entry.as_manifest().as_id().de()?);
|
||||
let version = entry.as_removing().as_manifest().as_version().de()?;
|
||||
tracing::debug!(
|
||||
"Cleaning up {:?} except for {:?}",
|
||||
volume_dir,
|
||||
dependents_paths
|
||||
);
|
||||
cleanup(ctx, id, &version).await?;
|
||||
cleanup_folder(volume_dir, Arc::new(dependents_paths)).await;
|
||||
remove_tor_keys(secrets, id).await?;
|
||||
|
||||
tracing::debug!("Cleaning up {:?} at {:?}", volumes, dependents_paths);
|
||||
cleanup_folder(volumes, Arc::new(dependents_paths)).await;
|
||||
remove_tor_keys(secrets, &entry.manifest.id).await?;
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
ctx.db
|
||||
.mutate(|d| {
|
||||
d.as_package_data_mut().remove(id)?;
|
||||
remove_from_current_dependents_lists(
|
||||
d,
|
||||
id,
|
||||
&entry.as_removing().as_current_dependencies().de()?,
|
||||
)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
@@ -373,8 +192,7 @@ pub async fn remove_tor_keys<Ex>(secrets: &mut Ex, id: &PackageId) -> Result<(),
|
||||
where
|
||||
for<'a> &'a mut Ex: Executor<'a, Database = Postgres>,
|
||||
{
|
||||
let id_str = id.as_str();
|
||||
sqlx::query!("DELETE FROM tor WHERE package = $1", id_str)
|
||||
sqlx::query!("DELETE FROM tor WHERE package = $1", &*id)
|
||||
.execute(secrets)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
BIN
backend/src/install/package-icon.png
Normal file
BIN
backend/src/install/package-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 280 KiB |
@@ -6,14 +6,16 @@ use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
|
||||
use patch_db::{DbHandle, HasModel, OptionModel, PatchDb};
|
||||
use models::{OptionExt, PackageId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
|
||||
|
||||
use crate::Error;
|
||||
use crate::db::model::Database;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct InstallProgress {
|
||||
pub size: Option<u64>,
|
||||
pub downloaded: AtomicU64,
|
||||
@@ -24,8 +26,8 @@ pub struct InstallProgress {
|
||||
pub unpack_complete: AtomicBool,
|
||||
}
|
||||
impl InstallProgress {
|
||||
pub fn new(size: Option<u64>) -> Arc<Self> {
|
||||
Arc::new(InstallProgress {
|
||||
pub fn new(size: Option<u64>) -> Self {
|
||||
InstallProgress {
|
||||
size,
|
||||
downloaded: AtomicU64::new(0),
|
||||
download_complete: AtomicBool::new(false),
|
||||
@@ -33,26 +35,25 @@ impl InstallProgress {
|
||||
validation_complete: AtomicBool::new(false),
|
||||
unpacked: AtomicU64::new(0),
|
||||
unpack_complete: AtomicBool::new(false),
|
||||
})
|
||||
}
|
||||
}
|
||||
pub fn download_complete(&self) {
|
||||
self.download_complete.store(true, Ordering::SeqCst)
|
||||
}
|
||||
pub async fn track_download<Db: DbHandle>(
|
||||
self: Arc<Self>,
|
||||
model: OptionModel<InstallProgress>,
|
||||
mut db: Db,
|
||||
) -> Result<(), Error> {
|
||||
pub async fn track_download(self: Arc<Self>, db: PatchDb, id: PackageId) -> Result<(), Error> {
|
||||
let update = |d: &mut Model<Database>| {
|
||||
d.as_package_data_mut()
|
||||
.as_idx_mut(&id)
|
||||
.or_not_found(&id)?
|
||||
.as_install_progress_mut()
|
||||
.or_not_found("install-progress")?
|
||||
.ser(&self)
|
||||
};
|
||||
while !self.download_complete.load(Ordering::SeqCst) {
|
||||
let mut tx = db.begin().await?;
|
||||
model.put(&mut tx, &self).await?;
|
||||
tx.save().await?;
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
db.mutate(&update).await?;
|
||||
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
}
|
||||
let mut tx = db.begin().await?;
|
||||
model.put(&mut tx, &self).await?;
|
||||
tx.save().await?;
|
||||
Ok(())
|
||||
db.mutate(&update).await
|
||||
}
|
||||
pub async fn track_download_during<
|
||||
F: FnOnce() -> Fut,
|
||||
@@ -60,33 +61,35 @@ impl InstallProgress {
|
||||
T,
|
||||
>(
|
||||
self: &Arc<Self>,
|
||||
model: OptionModel<InstallProgress>,
|
||||
db: &PatchDb,
|
||||
db: PatchDb,
|
||||
id: &PackageId,
|
||||
f: F,
|
||||
) -> Result<T, Error> {
|
||||
let local_db = db.handle();
|
||||
let tracker = tokio::spawn(self.clone().track_download(model.clone(), local_db));
|
||||
let tracker = tokio::spawn(self.clone().track_download(db.clone(), id.clone()));
|
||||
let res = f().await;
|
||||
self.download_complete.store(true, Ordering::SeqCst);
|
||||
tracker.await.unwrap()?;
|
||||
res
|
||||
}
|
||||
pub async fn track_read<Db: DbHandle>(
|
||||
pub async fn track_read(
|
||||
self: Arc<Self>,
|
||||
model: OptionModel<InstallProgress>,
|
||||
mut db: Db,
|
||||
db: PatchDb,
|
||||
id: PackageId,
|
||||
complete: Arc<AtomicBool>,
|
||||
) -> Result<(), Error> {
|
||||
let update = |d: &mut Model<Database>| {
|
||||
d.as_package_data_mut()
|
||||
.as_idx_mut(&id)
|
||||
.or_not_found(&id)?
|
||||
.as_install_progress_mut()
|
||||
.or_not_found("install-progress")?
|
||||
.ser(&self)
|
||||
};
|
||||
while !complete.load(Ordering::SeqCst) {
|
||||
let mut tx = db.begin().await?;
|
||||
model.put(&mut tx, &self).await?;
|
||||
tx.save().await?;
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
db.mutate(&update).await?;
|
||||
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
}
|
||||
let mut tx = db.begin().await?;
|
||||
model.put(&mut tx, &self).await?;
|
||||
tx.save().await?;
|
||||
Ok(())
|
||||
db.mutate(&update).await
|
||||
}
|
||||
pub async fn track_read_during<
|
||||
F: FnOnce() -> Fut,
|
||||
@@ -94,15 +97,14 @@ impl InstallProgress {
|
||||
T,
|
||||
>(
|
||||
self: &Arc<Self>,
|
||||
model: OptionModel<InstallProgress>,
|
||||
db: &PatchDb,
|
||||
db: PatchDb,
|
||||
id: &PackageId,
|
||||
f: F,
|
||||
) -> Result<T, Error> {
|
||||
let local_db = db.handle();
|
||||
let complete = Arc::new(AtomicBool::new(false));
|
||||
let tracker = tokio::spawn(self.clone().track_read(
|
||||
model.clone(),
|
||||
local_db,
|
||||
db.clone(),
|
||||
id.clone(),
|
||||
complete.clone(),
|
||||
));
|
||||
let res = f().await;
|
||||
|
||||
@@ -1,105 +1,18 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, Verifier};
|
||||
use rpc_toolkit::command;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::config::not_found;
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::CurrentDependents;
|
||||
use crate::dependencies::{
|
||||
break_transitive, BreakTransitiveReceipts, BreakageRes, DependencyError,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::display_serializable;
|
||||
use crate::util::Version;
|
||||
use crate::Error;
|
||||
|
||||
pub struct UpdateReceipts {
|
||||
break_receipts: BreakTransitiveReceipts,
|
||||
current_dependents: LockReceipt<CurrentDependents, String>,
|
||||
dependency: LockReceipt<crate::dependencies::DepInfo, (String, String)>,
|
||||
}
|
||||
|
||||
impl UpdateReceipts {
|
||||
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks);
|
||||
Ok(setup(&db.lock_all(locks).await?)?)
|
||||
}
|
||||
|
||||
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||
let break_receipts = BreakTransitiveReceipts::setup(locks);
|
||||
let current_dependents = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.current_dependents())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let dependency = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.star()
|
||||
.installed()
|
||||
.map(|x| x.manifest().dependencies().star())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
break_receipts: break_receipts(skeleton_key)?,
|
||||
current_dependents: current_dependents.verify(skeleton_key)?,
|
||||
dependency: dependency.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[command(subcommands(dry))]
|
||||
pub async fn update() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
#[command(display(display_serializable))]
|
||||
pub async fn dry(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg] id: PackageId,
|
||||
#[arg] version: Version,
|
||||
) -> Result<BreakageRes, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut tx = db.begin().await?;
|
||||
let mut breakages = BTreeMap::new();
|
||||
let receipts = UpdateReceipts::new(&mut tx).await?;
|
||||
|
||||
for dependent in receipts
|
||||
.current_dependents
|
||||
.get(&mut tx, &id)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(id))?
|
||||
.0
|
||||
.keys()
|
||||
.into_iter()
|
||||
.filter(|dependent| &&id != dependent)
|
||||
{
|
||||
if let Some(dep_info) = receipts.dependency.get(&mut tx, (&dependent, &id)).await? {
|
||||
let version_req = dep_info.version;
|
||||
if !version.satisfies(&version_req) {
|
||||
break_transitive(
|
||||
&mut tx,
|
||||
&dependent,
|
||||
&id,
|
||||
DependencyError::IncorrectVersion {
|
||||
expected: version_req,
|
||||
received: version.clone(),
|
||||
},
|
||||
&mut breakages,
|
||||
&receipts.break_receipts,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
tx.abort().await?;
|
||||
Ok(BreakageRes(breakages))
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ pub mod migration;
|
||||
pub mod net;
|
||||
pub mod notifications;
|
||||
pub mod os_install;
|
||||
pub mod prelude;
|
||||
pub mod procedure;
|
||||
pub mod properties;
|
||||
pub mod s9pk;
|
||||
@@ -105,7 +106,6 @@ pub fn server() -> Result<(), RpcError> {
|
||||
install::sideload,
|
||||
install::uninstall,
|
||||
install::list,
|
||||
install::update::update,
|
||||
config::config,
|
||||
control::start,
|
||||
control::stop,
|
||||
|
||||
@@ -1,111 +1,27 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use patch_db::{DbHandle, LockReceipt, LockType};
|
||||
use models::OptionExt;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::CurrentDependents;
|
||||
use crate::dependencies::{break_transitive, heal_transitive, DependencyError};
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::status::health_check::{HealthCheckId, HealthCheckResult};
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::status::MainStatus;
|
||||
use crate::Error;
|
||||
|
||||
struct HealthCheckPreInformationReceipt {
|
||||
status_model: LockReceipt<MainStatus, ()>,
|
||||
manifest: LockReceipt<Manifest, ()>,
|
||||
}
|
||||
impl HealthCheckPreInformationReceipt {
|
||||
pub async fn new(db: &'_ mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks, id);
|
||||
setup(&db.lock_all(locks).await?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
id: &PackageId,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let status_model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.status().main())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
let manifest = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.manifest())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
status_model: status_model.verify(skeleton_key)?,
|
||||
manifest: manifest.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HealthCheckStatusReceipt {
|
||||
status: LockReceipt<MainStatus, ()>,
|
||||
current_dependents: LockReceipt<CurrentDependents, ()>,
|
||||
}
|
||||
impl HealthCheckStatusReceipt {
|
||||
pub async fn new(db: &'_ mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
|
||||
let mut locks = Vec::new();
|
||||
|
||||
let setup = Self::setup(&mut locks, id);
|
||||
setup(&db.lock_all(locks).await?)
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
locks: &mut Vec<patch_db::LockTargetId>,
|
||||
id: &PackageId,
|
||||
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
|
||||
let status = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.status().main())
|
||||
.make_locker(LockType::Write)
|
||||
.add_to_keys(locks);
|
||||
let current_dependents = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|x| x.installed())
|
||||
.map(|x| x.current_dependents())
|
||||
.make_locker(LockType::Read)
|
||||
.add_to_keys(locks);
|
||||
move |skeleton_key| {
|
||||
Ok(Self {
|
||||
status: status.verify(skeleton_key)?,
|
||||
current_dependents: current_dependents.verify(skeleton_key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// So, this is used for a service to run a health check cycle, go out and run the health checks, and store those in the db
|
||||
#[instrument(skip_all)]
|
||||
pub async fn check<Db: DbHandle>(
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
id: &PackageId,
|
||||
) -> Result<(), Error> {
|
||||
let mut tx = db.begin().await?;
|
||||
pub async fn check(ctx: &RpcContext, id: &PackageId) -> Result<(), Error> {
|
||||
let (manifest, started) = {
|
||||
let mut checkpoint = tx.begin().await?;
|
||||
let receipts = HealthCheckPreInformationReceipt::new(&mut checkpoint, id).await?;
|
||||
let peeked = ctx.db.peek().await?;
|
||||
let pde = peeked
|
||||
.as_package_data()
|
||||
.as_idx(id)
|
||||
.or_not_found(id)?
|
||||
.expect_as_installed()?;
|
||||
|
||||
let manifest = receipts.manifest.get(&mut checkpoint).await?;
|
||||
let manifest = pde.as_installed().as_manifest().de()?;
|
||||
|
||||
let started = receipts.status_model.get(&mut checkpoint).await?.started();
|
||||
let started = pde.as_installed().as_status().as_main().de()?.started();
|
||||
|
||||
checkpoint.save().await?;
|
||||
(manifest, started)
|
||||
};
|
||||
|
||||
@@ -119,56 +35,22 @@ pub async fn check<Db: DbHandle>(
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let current_dependents = {
|
||||
let mut checkpoint = tx.begin().await?;
|
||||
let receipts = HealthCheckStatusReceipt::new(&mut checkpoint, id).await?;
|
||||
ctx.db
|
||||
.mutate(|v| {
|
||||
let pde = v
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(id)
|
||||
.or_not_found(id)?
|
||||
.expect_as_installed_mut()?;
|
||||
let status = pde.as_installed_mut().as_status_mut().as_main_mut();
|
||||
|
||||
let status = receipts.status.get(&mut checkpoint).await?;
|
||||
|
||||
if let MainStatus::Running { health: _, started } = status {
|
||||
receipts
|
||||
.status
|
||||
.set(
|
||||
&mut checkpoint,
|
||||
MainStatus::Running {
|
||||
health: health_results.clone(),
|
||||
started,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let current_dependents = receipts.current_dependents.get(&mut checkpoint).await?;
|
||||
|
||||
checkpoint.save().await?;
|
||||
current_dependents
|
||||
};
|
||||
|
||||
let receipts = crate::dependencies::BreakTransitiveReceipts::new(&mut tx).await?;
|
||||
|
||||
for (dependent, info) in (current_dependents).0.iter() {
|
||||
let failures: BTreeMap<HealthCheckId, HealthCheckResult> = health_results
|
||||
.iter()
|
||||
.filter(|(_, hc_res)| !matches!(hc_res, HealthCheckResult::Success { .. }))
|
||||
.filter(|(hc_id, _)| info.health_checks.contains(hc_id))
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
|
||||
if !failures.is_empty() {
|
||||
break_transitive(
|
||||
&mut tx,
|
||||
&dependent,
|
||||
id,
|
||||
DependencyError::HealthChecksFailed { failures },
|
||||
&mut BTreeMap::new(),
|
||||
&receipts,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
heal_transitive(ctx, &mut tx, &dependent, id, &receipts.dependency_receipt).await?;
|
||||
}
|
||||
}
|
||||
|
||||
tx.save().await?;
|
||||
|
||||
Ok(())
|
||||
if let MainStatus::Running { health: _, started } = status.de()? {
|
||||
status.ser(&MainStatus::Running {
|
||||
health: health_results.clone(),
|
||||
started,
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1,21 +1,39 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::FutureExt;
|
||||
use patch_db::PatchDbHandle;
|
||||
use models::OptionExt;
|
||||
use tokio::sync::watch;
|
||||
use tokio::sync::watch::Sender;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::start_stop::StartStop;
|
||||
use super::{manager_seed, run_main, ManagerPersistentContainer, RunMainResult};
|
||||
use crate::prelude::*;
|
||||
use crate::procedure::NoOutput;
|
||||
use crate::s9pk::manifest::Manifest;
|
||||
use crate::status::MainStatus;
|
||||
use crate::util::{GeneralBoxedGuard, NonDetachingJoinHandle};
|
||||
use crate::util::NonDetachingJoinHandle;
|
||||
use crate::Error;
|
||||
|
||||
pub type ManageContainerOverride = Arc<watch::Sender<Option<MainStatus>>>;
|
||||
pub type ManageContainerOverride = Arc<watch::Sender<Option<Override>>>;
|
||||
|
||||
pub type Override = MainStatus;
|
||||
|
||||
pub struct OverrideGuard {
|
||||
override_main_status: Option<ManageContainerOverride>,
|
||||
}
|
||||
impl OverrideGuard {
|
||||
pub fn drop(self) {}
|
||||
}
|
||||
impl Drop for OverrideGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(override_main_status) = self.override_main_status.take() {
|
||||
override_main_status.send_modify(|x| {
|
||||
*x = None;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the thing describing the state machine actor for a service
|
||||
/// state and current running/ desired states.
|
||||
@@ -32,10 +50,12 @@ impl ManageContainer {
|
||||
seed: Arc<manager_seed::ManagerSeed>,
|
||||
persistent_container: ManagerPersistentContainer,
|
||||
) -> Result<Self, Error> {
|
||||
let mut db = seed.ctx.db.handle();
|
||||
let current_state = Arc::new(watch::channel(StartStop::Stop).0);
|
||||
let desired_state = Arc::new(
|
||||
watch::channel::<StartStop>(get_status(&mut db, &seed.manifest).await.into()).0,
|
||||
watch::channel::<StartStop>(
|
||||
get_status(seed.ctx.db.peek().await?, &seed.manifest).into(),
|
||||
)
|
||||
.0,
|
||||
);
|
||||
let override_main_status: ManageContainerOverride = Arc::new(watch::channel(None).0);
|
||||
let service = tokio::spawn(create_service_manager(
|
||||
@@ -63,21 +83,30 @@ impl ManageContainer {
|
||||
|
||||
/// Set override is used during something like a restart of a service. We want to show certain statuses be different
|
||||
/// from the actual status of the service.
|
||||
pub fn set_override(&self, override_status: Option<MainStatus>) -> GeneralBoxedGuard {
|
||||
pub fn set_override(&self, override_status: Override) -> Result<OverrideGuard, Error> {
|
||||
let status = Some(override_status);
|
||||
if self.override_main_status.borrow().is_some() {
|
||||
return Err(Error::new(
|
||||
eyre!("Already have an override"),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
self.override_main_status
|
||||
.send_modify(|x| *x = override_status);
|
||||
let override_main_status = self.override_main_status.clone();
|
||||
GeneralBoxedGuard::new(move || {
|
||||
override_main_status.send_modify(|x| *x = None);
|
||||
.send_modify(|x| *x = status.clone());
|
||||
Ok(OverrideGuard {
|
||||
override_main_status: Some(self.override_main_status.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the override, but don't have a guard to revert it. Used only on the mananger to do a shutdown.
|
||||
pub(super) async fn lock_state_forever(&self, seed: &manager_seed::ManagerSeed) {
|
||||
let mut db = seed.ctx.db.handle();
|
||||
let current_state = get_status(&mut db, &seed.manifest).await;
|
||||
pub(super) async fn lock_state_forever(
|
||||
&self,
|
||||
seed: &manager_seed::ManagerSeed,
|
||||
) -> Result<(), Error> {
|
||||
let current_state = get_status(seed.ctx.db.peek().await?, &seed.manifest);
|
||||
self.override_main_status
|
||||
.send_modify(|x| *x = Some(current_state));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// We want to set the state of the service, like to start or stop
|
||||
@@ -159,7 +188,7 @@ async fn create_service_manager(
|
||||
async fn save_state(
|
||||
desired_state: Arc<Sender<StartStop>>,
|
||||
current_state: Arc<Sender<StartStop>>,
|
||||
override_main_status: Arc<Sender<Option<MainStatus>>>,
|
||||
override_main_status: ManageContainerOverride,
|
||||
seed: Arc<manager_seed::ManagerSeed>,
|
||||
) {
|
||||
let mut desired_state_receiver = desired_state.subscribe();
|
||||
@@ -169,31 +198,24 @@ async fn save_state(
|
||||
let current: StartStop = *current_state_receiver.borrow();
|
||||
let desired: StartStop = *desired_state_receiver.borrow();
|
||||
let override_status = override_main_status_receiver.borrow().clone();
|
||||
let mut db = seed.ctx.db.handle();
|
||||
let res = match (override_status, current, desired) {
|
||||
(Some(status), _, _) => set_status(&mut db, &seed.manifest, &status).await,
|
||||
(None, StartStop::Start, StartStop::Start) => {
|
||||
set_status(
|
||||
&mut db,
|
||||
&seed.manifest,
|
||||
&MainStatus::Running {
|
||||
started: chrono::Utc::now(),
|
||||
health: Default::default(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
(None, StartStop::Start, StartStop::Stop) => {
|
||||
set_status(&mut db, &seed.manifest, &MainStatus::Stopping).await
|
||||
}
|
||||
(None, StartStop::Stop, StartStop::Start) => {
|
||||
set_status(&mut db, &seed.manifest, &MainStatus::Starting).await
|
||||
}
|
||||
(None, StartStop::Stop, StartStop::Stop) => {
|
||||
set_status(&mut db, &seed.manifest, &MainStatus::Stopped).await
|
||||
}
|
||||
let status = match (override_status.clone(), current, desired) {
|
||||
(Some(status), _, _) => status,
|
||||
(_, StartStop::Start, StartStop::Start) => MainStatus::Running {
|
||||
started: chrono::Utc::now(),
|
||||
health: Default::default(),
|
||||
},
|
||||
(_, StartStop::Start, StartStop::Stop) => MainStatus::Stopping,
|
||||
(_, StartStop::Stop, StartStop::Start) => MainStatus::Starting,
|
||||
(_, StartStop::Stop, StartStop::Stop) => MainStatus::Stopped,
|
||||
};
|
||||
if let Err(err) = res {
|
||||
|
||||
let manifest = &seed.manifest;
|
||||
if let Err(err) = seed
|
||||
.ctx
|
||||
.db
|
||||
.mutate(|db| set_status(db, manifest, &status))
|
||||
.await
|
||||
{
|
||||
tracing::error!("Did not set status for {}", seed.container_name);
|
||||
tracing::debug!("{:?}", err);
|
||||
}
|
||||
@@ -238,40 +260,6 @@ async fn run_main_log_result(result: RunMainResult, seed: Arc<manager_seed::Mana
|
||||
match result {
|
||||
Ok(Ok(NoOutput)) => (), // restart
|
||||
Ok(Err(e)) => {
|
||||
#[cfg(feature = "unstable")]
|
||||
{
|
||||
use crate::notifications::NotificationLevel;
|
||||
let mut db = seed.ctx.db.handle();
|
||||
let started = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&seed.manifest.id)
|
||||
.and_then(|pde| pde.installed())
|
||||
.map::<_, MainStatus>(|i| i.status().main())
|
||||
.get(&mut db)
|
||||
.await;
|
||||
match started.as_deref() {
|
||||
Ok(Some(MainStatus::Running { .. })) => {
|
||||
let res = seed.ctx.notification_manager
|
||||
.notify(
|
||||
&mut db,
|
||||
Some(seed.manifest.id.clone()),
|
||||
NotificationLevel::Warning,
|
||||
String::from("Service Crashed"),
|
||||
format!("The service {} has crashed with the following exit code: {}\nDetails: {}", seed.manifest.id.clone(), e.0, e.1),
|
||||
(),
|
||||
Some(3600) // 1 hour
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = res {
|
||||
tracing::error!("Failed to issue notification: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
tracing::error!("service just started. not issuing crash notification")
|
||||
}
|
||||
}
|
||||
}
|
||||
tracing::error!(
|
||||
"The service {} has crashed with the following exit code: {}",
|
||||
seed.manifest.id.clone(),
|
||||
@@ -289,55 +277,24 @@ async fn run_main_log_result(result: RunMainResult, seed: Arc<manager_seed::Mana
|
||||
|
||||
/// Used only in the mod where we are doing a backup
|
||||
#[instrument(skip(db, manifest))]
|
||||
pub(super) async fn get_status(db: &mut PatchDbHandle, manifest: &Manifest) -> MainStatus {
|
||||
async move {
|
||||
Ok::<_, Error>(
|
||||
crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&manifest.id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?
|
||||
.status()
|
||||
.main()
|
||||
.get(db)
|
||||
.await?
|
||||
.clone(),
|
||||
)
|
||||
}
|
||||
.map(|x| x.unwrap_or_else(|_| MainStatus::Stopped))
|
||||
.await
|
||||
pub(super) fn get_status(db: Peeked, manifest: &Manifest) -> MainStatus {
|
||||
db.as_package_data()
|
||||
.as_idx(&manifest.id)
|
||||
.and_then(|x| x.as_installed())
|
||||
.filter(|x| x.as_manifest().as_version().de().ok() == Some(manifest.version.clone()))
|
||||
.and_then(|x| x.as_status().as_main().de().ok())
|
||||
.unwrap_or(MainStatus::Stopped)
|
||||
}
|
||||
|
||||
#[instrument(skip(db, manifest))]
|
||||
async fn set_status(
|
||||
db: &mut PatchDbHandle,
|
||||
manifest: &Manifest,
|
||||
main_status: &MainStatus,
|
||||
) -> Result<(), Error> {
|
||||
if crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&manifest.id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.exists(db)
|
||||
.await?
|
||||
{
|
||||
crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&manifest.id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?
|
||||
.status()
|
||||
.main()
|
||||
.put(db, main_status)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
fn set_status(db: &mut Peeked, manifest: &Manifest, main_status: &MainStatus) -> Result<(), Error> {
|
||||
let Some(installed) = db
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&manifest.id)
|
||||
.or_not_found(&manifest.id)?
|
||||
.as_installed_mut()
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
installed.as_status_mut().as_main_mut().ser(main_status)
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@ use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use patch_db::DbHandle;
|
||||
use sqlx::{Executor, Postgres};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::Manager;
|
||||
use crate::context::RpcContext;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::util::Version;
|
||||
use crate::Error;
|
||||
@@ -18,31 +17,16 @@ use crate::Error;
|
||||
pub struct ManagerMap(RwLock<BTreeMap<(PackageId, Version), Arc<Manager>>>);
|
||||
impl ManagerMap {
|
||||
#[instrument(skip_all)]
|
||||
pub async fn init<Db: DbHandle, Ex>(
|
||||
&self,
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
secrets: &mut Ex,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
for<'a> &'a mut Ex: Executor<'a, Database = Postgres>,
|
||||
{
|
||||
pub async fn init(&self, ctx: RpcContext, peeked: Peeked) -> Result<(), Error> {
|
||||
let mut res = BTreeMap::new();
|
||||
for package in crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.keys(db)
|
||||
.await?
|
||||
{
|
||||
let man: Manifest = if let Some(manifest) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&package)
|
||||
.and_then(|pkg| pkg.installed())
|
||||
.map(|m| m.manifest())
|
||||
.get(db)
|
||||
.await?
|
||||
.to_owned()
|
||||
for package in peeked.as_package_data().keys()? {
|
||||
let man: Manifest = if let Some(manifest) = peeked
|
||||
.as_package_data()
|
||||
.as_idx(&package)
|
||||
.and_then(|x| x.as_installed())
|
||||
.map(|x| x.as_manifest().de())
|
||||
{
|
||||
manifest
|
||||
manifest?
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
@@ -58,14 +42,15 @@ impl ManagerMap {
|
||||
|
||||
/// Used during the install process
|
||||
#[instrument(skip_all)]
|
||||
pub async fn add(&self, ctx: RpcContext, manifest: Manifest) -> Result<(), Error> {
|
||||
pub async fn add(&self, ctx: RpcContext, manifest: Manifest) -> Result<Arc<Manager>, Error> {
|
||||
let mut lock = self.0.write().await;
|
||||
let id = (manifest.id.clone(), manifest.version.clone());
|
||||
if let Some(man) = lock.remove(&id) {
|
||||
man.exit().await;
|
||||
}
|
||||
lock.insert(id, Arc::new(Manager::new(ctx, manifest).await?));
|
||||
Ok(())
|
||||
let manager = Arc::new(Manager::new(ctx.clone(), manifest).await?);
|
||||
lock.insert(id, manager.clone());
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
/// This is ran during the cleanup, so when we are uninstalling the service
|
||||
@@ -83,7 +68,7 @@ impl ManagerMap {
|
||||
futures::future::join_all(std::mem::take(&mut *self.0.write().await).into_iter().map(
|
||||
|((id, version), man)| async move {
|
||||
tracing::debug!("Manager for {}@{} shutting down", id, version);
|
||||
man.shutdown().await;
|
||||
man.shutdown().await?;
|
||||
tracing::debug!("Manager for {}@{} is shutdown", id, version);
|
||||
if let Err(e) = Arc::try_unwrap(man) {
|
||||
tracing::trace!(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use models::ErrorKind;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::s9pk::manifest::Manifest;
|
||||
|
||||
@@ -9,18 +9,14 @@ use embassy_container_init::ProcessGroupId;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{Future, FutureExt, TryFutureExt};
|
||||
use helpers::UnixRpcClient;
|
||||
use models::{ErrorKind, PackageId};
|
||||
use models::{ErrorKind, OptionExt, PackageId};
|
||||
use nix::sys::signal::Signal;
|
||||
use patch_db::DbHandle;
|
||||
use persistent_container::PersistentContainer;
|
||||
use rand::SeedableRng;
|
||||
use sqlx::Connection;
|
||||
use start_stop::StartStop;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::{
|
||||
watch::{self, Sender},
|
||||
Mutex,
|
||||
};
|
||||
use tokio::sync::watch::{self, Sender};
|
||||
use tokio::sync::{oneshot, Mutex};
|
||||
use tracing::instrument;
|
||||
use transition_state::TransitionState;
|
||||
|
||||
@@ -28,18 +24,18 @@ use crate::backup::target::PackageBackupInfo;
|
||||
use crate::backup::PackageBackupReport;
|
||||
use crate::config::action::ConfigRes;
|
||||
use crate::config::spec::ValueSpecPointer;
|
||||
use crate::config::{not_found, ConfigReceipts, ConfigureContext};
|
||||
use crate::config::ConfigureContext;
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::{CurrentDependencies, CurrentDependencyInfo};
|
||||
use crate::dependencies::{
|
||||
add_dependent_to_current_dependents_lists, break_transitive, heal_all_dependents_transitive,
|
||||
DependencyError, DependencyErrors, TaggedDependencyError,
|
||||
add_dependent_to_current_dependents_lists, compute_dependency_config_errs,
|
||||
};
|
||||
use crate::disk::mount::backup::BackupMountGuard;
|
||||
use crate::disk::mount::guard::TmpMountGuard;
|
||||
use crate::install::cleanup::remove_from_current_dependents_lists;
|
||||
use crate::net::net_controller::NetService;
|
||||
use crate::net::vhost::AlpnInfo;
|
||||
use crate::prelude::*;
|
||||
use crate::procedure::docker::{DockerContainer, DockerProcedure, LongRunning};
|
||||
use crate::procedure::{NoOutput, ProcedureName};
|
||||
use crate::s9pk::manifest::Manifest;
|
||||
@@ -152,7 +148,7 @@ impl Manager {
|
||||
self._transition_abort();
|
||||
self.manage_container.to_desired(StartStop::Stop);
|
||||
}
|
||||
pub async fn restart(&self) {
|
||||
pub fn restart(&self) {
|
||||
if self._is_transition_restart() {
|
||||
return;
|
||||
}
|
||||
@@ -161,7 +157,7 @@ impl Manager {
|
||||
pub async fn configure(
|
||||
&self,
|
||||
configure_context: ConfigureContext,
|
||||
) -> Result<BTreeMap<PackageId, TaggedDependencyError>, Error> {
|
||||
) -> Result<BTreeMap<PackageId, String>, Error> {
|
||||
if self._is_transition_configure() {
|
||||
return Ok(configure_context.breakages);
|
||||
}
|
||||
@@ -204,10 +200,11 @@ impl Manager {
|
||||
}
|
||||
|
||||
/// A special exit that is overridden the start state, should only be called in the shutdown, where we remove other containers
|
||||
async fn shutdown(&self) {
|
||||
self.manage_container.lock_state_forever(&self.seed).await;
|
||||
async fn shutdown(&self) -> Result<(), Error> {
|
||||
self.manage_container.lock_state_forever(&self.seed).await?;
|
||||
|
||||
self.exit().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Used when we want to shutdown the service
|
||||
@@ -229,7 +226,7 @@ impl Manager {
|
||||
.send_replace(Default::default())
|
||||
.join_handle()
|
||||
{
|
||||
(&**transition).abort();
|
||||
(**transition).abort();
|
||||
}
|
||||
}
|
||||
fn _transition_replace(&self, transition_state: TransitionState) {
|
||||
@@ -238,13 +235,14 @@ impl Manager {
|
||||
.abort();
|
||||
}
|
||||
|
||||
pub(super) fn perform_restart(&self) -> impl Future<Output = ()> + 'static {
|
||||
pub(super) fn perform_restart(&self) -> impl Future<Output = Result<(), Error>> + 'static {
|
||||
let manage_container = self.manage_container.clone();
|
||||
async move {
|
||||
let restart_override = manage_container.set_override(Some(MainStatus::Restarting));
|
||||
let restart_override = manage_container.set_override(MainStatus::Restarting)?;
|
||||
manage_container.wait_for_desired(StartStop::Stop).await;
|
||||
manage_container.wait_for_desired(StartStop::Start).await;
|
||||
drop(restart_override);
|
||||
restart_override.drop();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
fn _transition_restart(&self) -> TransitionState {
|
||||
@@ -252,7 +250,9 @@ impl Manager {
|
||||
let restart = self.perform_restart();
|
||||
TransitionState::Restarting(
|
||||
tokio::spawn(async move {
|
||||
restart.await;
|
||||
if let Err(err) = restart.await {
|
||||
tracing::error!("Error restarting service: {}", err);
|
||||
}
|
||||
transition.send_replace(Default::default());
|
||||
})
|
||||
.into(),
|
||||
@@ -261,37 +261,42 @@ impl Manager {
|
||||
fn perform_backup(
|
||||
&self,
|
||||
backup_guard: BackupGuard,
|
||||
) -> impl Future<Output = Result<Result<PackageBackupInfo, Error>, Error>> + 'static {
|
||||
) -> impl Future<Output = Result<Result<PackageBackupInfo, Error>, Error>> {
|
||||
let manage_container = self.manage_container.clone();
|
||||
let seed = self.seed.clone();
|
||||
async move {
|
||||
let peek = seed.ctx.db.peek().await?;
|
||||
let state_reverter = DesiredStateReverter::new(manage_container.clone());
|
||||
let mut tx = seed.ctx.db.handle();
|
||||
let override_guard = manage_container
|
||||
.set_override(Some(get_status(&mut tx, &seed.manifest).await.backing_up()));
|
||||
let override_guard =
|
||||
manage_container.set_override(get_status(peek, &seed.manifest).backing_up())?;
|
||||
manage_container.wait_for_desired(StartStop::Stop).await;
|
||||
let backup_guard = backup_guard.lock().await;
|
||||
let guard = backup_guard.mount_package_backup(&seed.manifest.id).await?;
|
||||
|
||||
let res = seed
|
||||
.manifest
|
||||
.backup
|
||||
.create(
|
||||
&seed.ctx,
|
||||
&mut tx,
|
||||
&seed.manifest.id,
|
||||
&seed.manifest.title,
|
||||
&seed.manifest.version,
|
||||
&seed.manifest.interfaces,
|
||||
&seed.manifest.volumes,
|
||||
)
|
||||
.await;
|
||||
let return_value = seed.manifest.backup.create(seed.clone()).await;
|
||||
guard.unmount().await?;
|
||||
drop(backup_guard);
|
||||
|
||||
let return_value = res;
|
||||
let manifest_id = seed.manifest.id.clone();
|
||||
seed.ctx
|
||||
.db
|
||||
.mutate(|db| {
|
||||
if let Some(progress) = db
|
||||
.as_server_info_mut()
|
||||
.as_status_info_mut()
|
||||
.as_backup_progress_mut()
|
||||
.transpose_mut()
|
||||
.and_then(|p| p.as_idx_mut(&manifest_id))
|
||||
{
|
||||
progress.as_complete_mut().ser(&true)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
state_reverter.revert().await;
|
||||
drop(override_guard);
|
||||
|
||||
override_guard.drop();
|
||||
Ok::<_, Error>(return_value)
|
||||
}
|
||||
}
|
||||
@@ -300,11 +305,13 @@ impl Manager {
|
||||
backup_guard: BackupGuard,
|
||||
) -> (TransitionState, BoxFuture<BackupReturn>) {
|
||||
let (send, done) = oneshot::channel();
|
||||
|
||||
let transition_state = self.transition.clone();
|
||||
(
|
||||
TransitionState::BackingUp(
|
||||
tokio::spawn(
|
||||
self.perform_backup(backup_guard)
|
||||
.then(finish_up_backup_task(self.transition.clone(), send)),
|
||||
.then(finish_up_backup_task(transition_state, send)),
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
@@ -332,42 +339,29 @@ async fn configure(
|
||||
ctx: RpcContext,
|
||||
id: PackageId,
|
||||
mut configure_context: ConfigureContext,
|
||||
) -> Result<BTreeMap<PackageId, TaggedDependencyError>, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut tx = db.begin().await?;
|
||||
let db = &mut tx;
|
||||
|
||||
let receipts = ConfigReceipts::new(db).await?;
|
||||
) -> Result<BTreeMap<PackageId, String>, Error> {
|
||||
let db = ctx.db.peek().await?;
|
||||
let id = &id;
|
||||
let ctx = &ctx;
|
||||
let overrides = &mut configure_context.overrides;
|
||||
// fetch data from db
|
||||
let action = receipts
|
||||
.config_actions
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(id))?;
|
||||
let dependencies = receipts
|
||||
.dependencies
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(id))?;
|
||||
let volumes = receipts
|
||||
.volumes
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(id))?;
|
||||
let version = receipts
|
||||
.version
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(id))?;
|
||||
let manifest = db
|
||||
.as_package_data()
|
||||
.as_idx(id)
|
||||
.or_not_found(id)?
|
||||
.as_manifest()
|
||||
.de()?;
|
||||
|
||||
// get current config and current spec
|
||||
let ConfigRes {
|
||||
config: old_config,
|
||||
spec,
|
||||
} = action.get(ctx, id, &version, &volumes).await?;
|
||||
} = manifest
|
||||
.config
|
||||
.as_ref()
|
||||
.or_not_found("Manifest config")?
|
||||
.get(ctx, id, &manifest.version, &manifest.volumes)
|
||||
.await?;
|
||||
|
||||
// determine new config to use
|
||||
let mut config = if let Some(config) = configure_context.config.or_else(|| old_config.clone()) {
|
||||
@@ -379,31 +373,22 @@ async fn configure(
|
||||
)?
|
||||
};
|
||||
|
||||
let manifest = receipts
|
||||
.manifest
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(id))?;
|
||||
|
||||
spec.validate(&manifest)?;
|
||||
spec.matches(&config)?; // check that new config matches spec
|
||||
spec.update(
|
||||
ctx,
|
||||
db,
|
||||
&manifest,
|
||||
overrides,
|
||||
&mut config,
|
||||
&receipts.config_receipts,
|
||||
)
|
||||
.await?; // dereference pointers in the new config
|
||||
|
||||
// create backreferences to pointers
|
||||
let mut sys = receipts
|
||||
.system_pointers
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(id))?;
|
||||
sys.truncate(0);
|
||||
// TODO Commit or not?
|
||||
spec.update(ctx, &manifest, overrides, &mut config).await?; // dereference pointers in the new config
|
||||
|
||||
let manifest = db
|
||||
.as_package_data()
|
||||
.as_idx(id)
|
||||
.or_not_found(id)?
|
||||
.as_installed()
|
||||
.or_not_found(id)?
|
||||
.as_manifest()
|
||||
.de()?;
|
||||
|
||||
let dependencies = &manifest.dependencies;
|
||||
let mut current_dependencies: CurrentDependencies = CurrentDependencies(
|
||||
dependencies
|
||||
.0
|
||||
@@ -420,29 +405,32 @@ async fn configure(
|
||||
for ptr in spec.pointers(&config)? {
|
||||
match ptr {
|
||||
ValueSpecPointer::Package(pkg_ptr) => {
|
||||
if let Some(current_dependency) =
|
||||
current_dependencies.0.get_mut(pkg_ptr.package_id())
|
||||
{
|
||||
current_dependency.pointers.push(pkg_ptr);
|
||||
if let Some(info) = current_dependencies.0.get_mut(pkg_ptr.package_id()) {
|
||||
info.pointers.insert(pkg_ptr);
|
||||
} else {
|
||||
let id = pkg_ptr.package_id().to_owned();
|
||||
let mut pointers = BTreeSet::new();
|
||||
pointers.insert(pkg_ptr);
|
||||
current_dependencies.0.insert(
|
||||
pkg_ptr.package_id().to_owned(),
|
||||
id,
|
||||
CurrentDependencyInfo {
|
||||
pointers: vec![pkg_ptr],
|
||||
pointers,
|
||||
health_checks: BTreeSet::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
ValueSpecPointer::System(s) => sys.push(s),
|
||||
ValueSpecPointer::System(_) => (),
|
||||
}
|
||||
}
|
||||
receipts.system_pointers.set(db, sys, id).await?;
|
||||
|
||||
let action = manifest.config.as_ref().or_not_found(id)?;
|
||||
let version = &manifest.version;
|
||||
let volumes = &manifest.volumes;
|
||||
if !configure_context.dry_run {
|
||||
// run config action
|
||||
let res = action
|
||||
.set(ctx, id, &version, &dependencies, &volumes, &config)
|
||||
.set(ctx, id, version, &dependencies, volumes, &config)
|
||||
.await?;
|
||||
|
||||
// track dependencies with no pointers
|
||||
@@ -453,7 +441,7 @@ async fn configure(
|
||||
current_dependencies.0.insert(
|
||||
package_id,
|
||||
CurrentDependencyInfo {
|
||||
pointers: Vec::new(),
|
||||
pointers: BTreeSet::new(),
|
||||
health_checks,
|
||||
},
|
||||
);
|
||||
@@ -475,47 +463,9 @@ async fn configure(
|
||||
});
|
||||
}
|
||||
|
||||
// update dependencies
|
||||
let prev_current_dependencies = receipts
|
||||
.current_dependencies
|
||||
.get(db, id)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
remove_from_current_dependents_lists(
|
||||
db,
|
||||
id,
|
||||
&prev_current_dependencies,
|
||||
&receipts.current_dependents,
|
||||
)
|
||||
.await?; // remove previous
|
||||
add_dependent_to_current_dependents_lists(
|
||||
db,
|
||||
id,
|
||||
¤t_dependencies,
|
||||
&receipts.current_dependents,
|
||||
)
|
||||
.await?; // add new
|
||||
current_dependencies.0.remove(id);
|
||||
receipts
|
||||
.current_dependencies
|
||||
.set(db, current_dependencies.clone(), id)
|
||||
.await?;
|
||||
|
||||
let errs = receipts
|
||||
.dependency_errors
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(id))?;
|
||||
tracing::warn!("Dependency Errors: {:?}", errs);
|
||||
let errs = DependencyErrors::init(
|
||||
ctx,
|
||||
db,
|
||||
&manifest,
|
||||
¤t_dependencies,
|
||||
&receipts.dependency_receipt.try_heal,
|
||||
)
|
||||
.await?;
|
||||
receipts.dependency_errors.set(db, errs, id).await?;
|
||||
let dependency_config_errs =
|
||||
compute_dependency_config_errs(&ctx, &db, &manifest, ¤t_dependencies, overrides)
|
||||
.await?;
|
||||
|
||||
// cache current config for dependents
|
||||
configure_context
|
||||
@@ -523,29 +473,41 @@ async fn configure(
|
||||
.insert(id.clone(), config.clone());
|
||||
|
||||
// handle dependents
|
||||
let dependents = receipts
|
||||
.current_dependents
|
||||
.get(db, id)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(id))?;
|
||||
|
||||
let dependents = db
|
||||
.as_package_data()
|
||||
.as_idx(id)
|
||||
.or_not_found(id)?
|
||||
.as_installed()
|
||||
.or_not_found(id)?
|
||||
.as_current_dependents()
|
||||
.de()?;
|
||||
for (dependent, _dep_info) in dependents.0.iter().filter(|(dep_id, _)| dep_id != &id) {
|
||||
let dependent_container = receipts.docker_containers.get(db, dependent).await?;
|
||||
let dependent_container = &dependent_container;
|
||||
// check if config passes dependent check
|
||||
if let Some(cfg) = receipts
|
||||
.manifest_dependencies_config
|
||||
.get(db, (dependent, id))
|
||||
.await?
|
||||
if let Some(cfg) = db
|
||||
.as_package_data()
|
||||
.as_idx(dependent)
|
||||
.or_not_found(dependent)?
|
||||
.as_installed()
|
||||
.or_not_found(dependent)?
|
||||
.as_manifest()
|
||||
.as_dependencies()
|
||||
.as_idx(id)
|
||||
.or_not_found(id)?
|
||||
.as_config()
|
||||
.de()?
|
||||
{
|
||||
let manifest = receipts
|
||||
.manifest
|
||||
.get(db, dependent)
|
||||
.await?
|
||||
.ok_or_else(|| not_found!(id))?;
|
||||
let manifest = db
|
||||
.as_package_data()
|
||||
.as_idx(dependent)
|
||||
.or_not_found(dependent)?
|
||||
.as_installed()
|
||||
.or_not_found(dependent)?
|
||||
.as_manifest()
|
||||
.de()?;
|
||||
if let Err(error) = cfg
|
||||
.check(
|
||||
ctx,
|
||||
dependent_container,
|
||||
dependent,
|
||||
&manifest.version,
|
||||
&manifest.volumes,
|
||||
@@ -554,28 +516,49 @@ async fn configure(
|
||||
)
|
||||
.await?
|
||||
{
|
||||
let dep_err = DependencyError::ConfigUnsatisfied { error };
|
||||
break_transitive(
|
||||
db,
|
||||
dependent,
|
||||
id,
|
||||
dep_err,
|
||||
&mut configure_context.breakages,
|
||||
&receipts.break_transitive_receipts,
|
||||
)
|
||||
.await?;
|
||||
configure_context.breakages.insert(dependent.clone(), error);
|
||||
}
|
||||
|
||||
heal_all_dependents_transitive(ctx, db, id, &receipts.dependency_receipt).await?;
|
||||
}
|
||||
}
|
||||
|
||||
receipts.configured.set(db, true, id).await?;
|
||||
|
||||
if configure_context.dry_run {
|
||||
tx.abort().await?;
|
||||
} else {
|
||||
tx.commit().await?;
|
||||
if !configure_context.dry_run {
|
||||
return ctx
|
||||
.db
|
||||
.mutate(move |db| {
|
||||
remove_from_current_dependents_lists(db, id, ¤t_dependencies)?;
|
||||
add_dependent_to_current_dependents_lists(db, id, ¤t_dependencies)?;
|
||||
current_dependencies.0.remove(id);
|
||||
for (dep, errs) in db
|
||||
.as_package_data_mut()
|
||||
.as_entries_mut()?
|
||||
.into_iter()
|
||||
.filter_map(|(id, pde)| {
|
||||
pde.as_installed_mut()
|
||||
.map(|i| (id, i.as_status_mut().as_dependency_config_errors_mut()))
|
||||
})
|
||||
{
|
||||
errs.remove(id)?;
|
||||
if let Some(err) = configure_context.breakages.get(&dep) {
|
||||
errs.insert(id, err)?;
|
||||
}
|
||||
}
|
||||
let installed = db
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(id)
|
||||
.or_not_found(id)?
|
||||
.as_installed_mut()
|
||||
.or_not_found(id)?;
|
||||
installed
|
||||
.as_current_dependencies_mut()
|
||||
.ser(¤t_dependencies)?;
|
||||
let status = installed.as_status_mut();
|
||||
status.as_configured_mut().ser(&true)?;
|
||||
status
|
||||
.as_dependency_config_errors_mut()
|
||||
.ser(&dependency_config_errs)?;
|
||||
Ok(configure_context.breakages)
|
||||
})
|
||||
.await; // add new
|
||||
}
|
||||
|
||||
Ok(configure_context.breakages)
|
||||
@@ -746,7 +729,7 @@ async fn get_long_running_ip(seed: &ManagerSeed, runtime: &mut LongRunning) -> G
|
||||
Ok(Some(ip_addr)) => return GetRunningIp::Ip(ip_addr),
|
||||
Ok(None) => (),
|
||||
Err(e) if e.kind == ErrorKind::NotFound => (),
|
||||
Err(e) => return GetRunningIp::Error(e.into()),
|
||||
Err(e) => return GetRunningIp::Error(e),
|
||||
}
|
||||
if let Poll::Ready(res) = futures::poll!(&mut runtime.running_output) {
|
||||
match res {
|
||||
@@ -808,8 +791,7 @@ async fn remove_network_for_main(svc: NetService) -> Result<(), Error> {
|
||||
async fn main_health_check_daemon(seed: Arc<ManagerSeed>) {
|
||||
tokio::time::sleep(Duration::from_secs(HEALTH_CHECK_GRACE_PERIOD_SECONDS)).await;
|
||||
loop {
|
||||
let mut db = seed.ctx.db.handle();
|
||||
if let Err(e) = health::check(&seed.ctx, &mut db, &seed.manifest.id).await {
|
||||
if let Err(e) = health::check(&seed.ctx, &seed.manifest.id).await {
|
||||
tracing::error!(
|
||||
"Failed to run health check for {}: {}",
|
||||
&seed.manifest.id,
|
||||
@@ -830,7 +812,7 @@ async fn get_running_ip(seed: &ManagerSeed, mut runtime: &mut RuntimeOfCommand)
|
||||
Ok(Some(ip_addr)) => return GetRunningIp::Ip(ip_addr),
|
||||
Ok(None) => (),
|
||||
Err(e) if e.kind == ErrorKind::NotFound => (),
|
||||
Err(e) => return GetRunningIp::Error(e.into()),
|
||||
Err(e) => return GetRunningIp::Error(e),
|
||||
}
|
||||
if let Poll::Ready(res) = futures::poll!(&mut runtime) {
|
||||
match res {
|
||||
|
||||
@@ -18,9 +18,15 @@ impl From<MainStatus> for StartStop {
|
||||
MainStatus::Restarting => StartStop::Start,
|
||||
MainStatus::Stopping => StartStop::Stop,
|
||||
MainStatus::Starting => StartStop::Start,
|
||||
MainStatus::Running { started, health } => StartStop::Start,
|
||||
MainStatus::BackingUp { started, health } if started.is_some() => StartStop::Start,
|
||||
MainStatus::BackingUp { started, health } => StartStop::Stop,
|
||||
MainStatus::Running {
|
||||
started: _,
|
||||
health: _,
|
||||
} => StartStop::Start,
|
||||
MainStatus::BackingUp { started, health: _ } if started.is_some() => StartStop::Start,
|
||||
MainStatus::BackingUp {
|
||||
started: _,
|
||||
health: _,
|
||||
} => StartStop::Stop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn marketplace() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn with_query_params(ctx: &RpcContext, mut url: Url) -> Url {
|
||||
pub fn with_query_params(ctx: RpcContext, mut url: Url) -> Url {
|
||||
url.query_pairs_mut()
|
||||
.append_pair(
|
||||
"os.version",
|
||||
@@ -38,7 +38,7 @@ pub fn with_query_params(ctx: &RpcContext, mut url: Url) -> Url {
|
||||
pub async fn get(#[context] ctx: RpcContext, #[arg] url: Url) -> Result<Value, Error> {
|
||||
let mut response = ctx
|
||||
.client
|
||||
.get(with_query_params(&ctx, url))
|
||||
.get(with_query_params(ctx.clone(), url))
|
||||
.send()
|
||||
.await
|
||||
.with_kind(crate::ErrorKind::Network)?;
|
||||
|
||||
@@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::prelude::*;
|
||||
use crate::procedure::docker::DockerContainers;
|
||||
use crate::procedure::{PackageProcedure, ProcedureName};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
@@ -19,6 +20,7 @@ use crate::{Error, ResultExt};
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct Migrations {
|
||||
pub from: IndexMap<VersionRange, PackageProcedure>,
|
||||
pub to: IndexMap<VersionRange, PackageProcedure>,
|
||||
@@ -27,7 +29,7 @@ impl Migrations {
|
||||
#[instrument(skip_all)]
|
||||
pub fn validate(
|
||||
&self,
|
||||
container: &Option<DockerContainers>,
|
||||
_container: &Option<DockerContainers>,
|
||||
eos_version: &Version,
|
||||
volumes: &Volumes,
|
||||
image_ids: &BTreeSet<ImageId>,
|
||||
@@ -58,7 +60,7 @@ impl Migrations {
|
||||
#[instrument(skip_all)]
|
||||
pub fn from<'a>(
|
||||
&'a self,
|
||||
container: &'a Option<DockerContainers>,
|
||||
_container: &'a Option<DockerContainers>,
|
||||
ctx: &'a RpcContext,
|
||||
version: &'a Version,
|
||||
pkg_id: &'a PackageId,
|
||||
@@ -133,6 +135,7 @@ impl Migrations {
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct MigrationRes {
|
||||
pub configured: bool,
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use tokio::sync::RwLock;
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::IpInfo;
|
||||
use crate::net::utils::{iface_is_physical, list_interfaces};
|
||||
use crate::prelude::*;
|
||||
use crate::util::display_none;
|
||||
use crate::Error;
|
||||
|
||||
@@ -58,12 +59,14 @@ pub async fn dhcp() -> Result<(), Error> {
|
||||
pub async fn update(#[context] ctx: RpcContext, #[arg] interface: String) -> Result<(), Error> {
|
||||
if iface_is_physical(&interface).await {
|
||||
let ip_info = IpInfo::for_interface(&interface).await?;
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.ip_info()
|
||||
.idx_model(&interface)
|
||||
.put(&mut ctx.db.handle(), &ip_info)
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_server_info_mut()
|
||||
.as_ip_info_mut()
|
||||
.insert(&interface, &ip_info)
|
||||
})
|
||||
.await?;
|
||||
|
||||
let mut cached = CACHED_IPS.write().await;
|
||||
if cached.is_empty() {
|
||||
*cached = _ips().await?;
|
||||
|
||||
@@ -50,17 +50,15 @@ impl Resolver {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Some(ip) = self.services.read().await.get(&None) {
|
||||
Some(
|
||||
ip.iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.map(|(ip, _)| *ip)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
if let Some(ip) = self.services.read().await.get(&None) {
|
||||
Some(
|
||||
ip.iter()
|
||||
.filter(|(_, rc)| rc.strong_count() > 0)
|
||||
.map(|(ip, _)| *ip)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
|
||||
@@ -4,10 +4,10 @@ use indexmap::IndexSet;
|
||||
pub use models::InterfaceId;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use sqlx::{Executor, Postgres};
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::db::model::{InterfaceAddressMap, InterfaceAddresses};
|
||||
use crate::net::keys::Key;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::serde::Port;
|
||||
use crate::{Error, ResultExt};
|
||||
@@ -44,33 +44,13 @@ impl Interfaces {
|
||||
lan_address: None,
|
||||
};
|
||||
if iface.tor_config.is_some() || iface.lan_config.is_some() {
|
||||
let key = TorSecretKeyV3::generate();
|
||||
let key_vec = key.as_bytes().to_vec();
|
||||
sqlx::query!(
|
||||
"INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING",
|
||||
**package_id,
|
||||
**id,
|
||||
key_vec,
|
||||
)
|
||||
.execute(&mut *secrets)
|
||||
.await?;
|
||||
let key_row = sqlx::query!(
|
||||
"SELECT key FROM tor WHERE package = $1 AND interface = $2",
|
||||
**package_id,
|
||||
**id,
|
||||
)
|
||||
.fetch_one(&mut *secrets)
|
||||
.await?;
|
||||
let mut key = [0_u8; 64];
|
||||
key.clone_from_slice(&key_row.key);
|
||||
let key = TorSecretKeyV3::from(key);
|
||||
let onion = key.public().get_onion_address();
|
||||
let key =
|
||||
Key::for_interface(secrets, Some((package_id.clone(), id.clone()))).await?;
|
||||
if iface.tor_config.is_some() {
|
||||
addrs.tor_address = Some(onion.to_string());
|
||||
addrs.tor_address = Some(key.tor_address().to_string());
|
||||
}
|
||||
if iface.lan_config.is_some() {
|
||||
addrs.lan_address =
|
||||
Some(format!("{}.local", onion.get_address_without_dot_onion()));
|
||||
addrs.lan_address = Some(key.local_address());
|
||||
}
|
||||
}
|
||||
interface_addresses.0.insert(id.clone(), addrs);
|
||||
|
||||
@@ -21,8 +21,8 @@ async fn compat(
|
||||
if let Some((package, interface)) = interface {
|
||||
if let Some(r) = sqlx::query!(
|
||||
"SELECT key FROM tor WHERE package = $1 AND interface = $2",
|
||||
**package,
|
||||
**interface
|
||||
package,
|
||||
interface
|
||||
)
|
||||
.fetch_optional(secrets)
|
||||
.await?
|
||||
@@ -31,16 +31,14 @@ async fn compat(
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else if let Some(key) = sqlx::query!("SELECT tor_key FROM account WHERE id = 0")
|
||||
.fetch_one(secrets)
|
||||
.await?
|
||||
.tor_key
|
||||
{
|
||||
Ok(Some(ExpandedSecretKey::from_bytes(&key)?))
|
||||
} else {
|
||||
if let Some(key) = sqlx::query!("SELECT tor_key FROM account WHERE id = 0")
|
||||
.fetch_one(secrets)
|
||||
.await?
|
||||
.tor_key
|
||||
{
|
||||
Ok(Some(ExpandedSecretKey::from_bytes(&key)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +148,7 @@ impl Key {
|
||||
WHERE
|
||||
network_keys.package = $1
|
||||
"#,
|
||||
**package
|
||||
package
|
||||
)
|
||||
.fetch_all(secrets)
|
||||
.await?
|
||||
@@ -194,8 +192,8 @@ impl Key {
|
||||
let k = tentative.as_slice();
|
||||
let actual = sqlx::query!(
|
||||
"INSERT INTO network_keys (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO UPDATE SET package = EXCLUDED.package RETURNING key",
|
||||
**pkg,
|
||||
**iface,
|
||||
pkg,
|
||||
iface,
|
||||
k,
|
||||
)
|
||||
.fetch_one(&mut *secrets)
|
||||
|
||||
@@ -617,7 +617,7 @@ async fn torctl(
|
||||
let mut last_success = Instant::now();
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
if let Err(e) = tokio::time::timeout(
|
||||
if tokio::time::timeout(
|
||||
Duration::from_secs(30),
|
||||
tokio_socks::tcp::Socks5Stream::connect(
|
||||
tor_socks,
|
||||
@@ -627,6 +627,7 @@ async fn torctl(
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.and_then(|e| e.map_err(|e| e.to_string()))
|
||||
.is_err()
|
||||
{
|
||||
if last_success.elapsed() > *health_timeout {
|
||||
let err = Error::new(eyre!("Tor health check failed for longer than current timeout ({health_timeout:?})"), crate::ErrorKind::Tor);
|
||||
|
||||
@@ -6,7 +6,6 @@ use std::time::Duration;
|
||||
use clap::ArgMatches;
|
||||
use isocountry::CountryCode;
|
||||
use lazy_static::lazy_static;
|
||||
use patch_db::DbHandle;
|
||||
use regex::Regex;
|
||||
use rpc_toolkit::command;
|
||||
use tokio::process::Command;
|
||||
@@ -14,6 +13,7 @@ use tokio::sync::RwLock;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::{display_serializable, IoFormat};
|
||||
use crate::util::{display_none, Invoke};
|
||||
use crate::{Error, ErrorKind};
|
||||
@@ -52,8 +52,6 @@ pub async fn add(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg] ssid: String,
|
||||
#[arg] password: String,
|
||||
#[arg] priority: isize,
|
||||
#[arg] connect: bool,
|
||||
) -> Result<(), Error> {
|
||||
let wifi_manager = wifi_manager(&ctx)?;
|
||||
if !ssid.is_ascii() {
|
||||
@@ -69,26 +67,22 @@ pub async fn add(
|
||||
));
|
||||
}
|
||||
async fn add_procedure(
|
||||
db: impl DbHandle,
|
||||
db: PatchDb,
|
||||
wifi_manager: WifiManager,
|
||||
ssid: &Ssid,
|
||||
password: &Psk,
|
||||
priority: isize,
|
||||
) -> Result<(), Error> {
|
||||
tracing::info!("Adding new WiFi network: '{}'", ssid.0);
|
||||
let mut wpa_supplicant = wifi_manager.write().await;
|
||||
wpa_supplicant
|
||||
.add_network(db, ssid, password, priority)
|
||||
.await?;
|
||||
wpa_supplicant.add_network(db, ssid, password).await?;
|
||||
drop(wpa_supplicant);
|
||||
Ok(())
|
||||
}
|
||||
if let Err(err) = add_procedure(
|
||||
&mut ctx.db.handle(),
|
||||
ctx.db.clone(),
|
||||
wifi_manager.clone(),
|
||||
&Ssid(ssid.clone()),
|
||||
&Psk(password.clone()),
|
||||
priority,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -113,7 +107,7 @@ pub async fn connect(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result<
|
||||
));
|
||||
}
|
||||
async fn connect_procedure(
|
||||
mut db: impl DbHandle,
|
||||
db: PatchDb,
|
||||
wifi_manager: WifiManager,
|
||||
ssid: &Ssid,
|
||||
) -> Result<(), Error> {
|
||||
@@ -121,7 +115,7 @@ pub async fn connect(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result<
|
||||
let current = wpa_supplicant.get_current_network().await?;
|
||||
drop(wpa_supplicant);
|
||||
let mut wpa_supplicant = wifi_manager.write().await;
|
||||
let connected = wpa_supplicant.select_network(&mut db, ssid).await?;
|
||||
let connected = wpa_supplicant.select_network(db.clone(), ssid).await?;
|
||||
if connected {
|
||||
tracing::info!("Successfully connected to WiFi: '{}'", ssid.0);
|
||||
} else {
|
||||
@@ -131,19 +125,15 @@ pub async fn connect(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result<
|
||||
tracing::info!("No WiFi to revert to!");
|
||||
}
|
||||
Some(current) => {
|
||||
wpa_supplicant.select_network(&mut db, ¤t).await?;
|
||||
wpa_supplicant.select_network(db, ¤t).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if let Err(err) = connect_procedure(
|
||||
&mut ctx.db.handle(),
|
||||
wifi_manager.clone(),
|
||||
&Ssid(ssid.clone()),
|
||||
)
|
||||
.await
|
||||
if let Err(err) =
|
||||
connect_procedure(ctx.db.clone(), wifi_manager.clone(), &Ssid(ssid.clone())).await
|
||||
{
|
||||
tracing::error!("Failed to connect to WiFi network '{}': {}", &ssid, err);
|
||||
return Err(Error::new(
|
||||
@@ -176,9 +166,7 @@ pub async fn delete(#[context] ctx: RpcContext, #[arg] ssid: String) -> Result<(
|
||||
return Err(Error::new(color_eyre::eyre::eyre!("Forbidden: Deleting this network would make your server unreachable. Either connect to ethernet or connect to a different WiFi network to remedy this."), ErrorKind::Wifi));
|
||||
}
|
||||
|
||||
wpa_supplicant
|
||||
.remove_network(&mut ctx.db.handle(), &ssid)
|
||||
.await?;
|
||||
wpa_supplicant.remove_network(ctx.db.clone(), &ssid).await?;
|
||||
Ok(())
|
||||
}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
@@ -397,7 +385,7 @@ pub async fn set_country(
|
||||
}
|
||||
wpa_supplicant.remove_all_connections().await?;
|
||||
|
||||
wpa_supplicant.save_config(&mut ctx.db.handle()).await?;
|
||||
wpa_supplicant.save_config(ctx.db.clone()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -645,13 +633,14 @@ impl WpaCli {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub async fn save_config(&mut self, mut db: impl DbHandle) -> Result<(), Error> {
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.last_wifi_region()
|
||||
.put(&mut db, &Some(self.get_country_low().await?))
|
||||
.await?;
|
||||
Ok(())
|
||||
pub async fn save_config(&mut self, db: PatchDb) -> Result<(), Error> {
|
||||
let new_country = Some(self.get_country_low().await?);
|
||||
db.mutate(|d| {
|
||||
d.as_server_info_mut()
|
||||
.as_last_wifi_region_mut()
|
||||
.ser(&new_country)
|
||||
})
|
||||
.await
|
||||
}
|
||||
async fn check_active_network(&self, ssid: &Ssid) -> Result<Option<NetworkId>, Error> {
|
||||
Ok(self
|
||||
@@ -682,7 +671,7 @@ impl WpaCli {
|
||||
.collect())
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
pub async fn select_network(&mut self, db: impl DbHandle, ssid: &Ssid) -> Result<bool, Error> {
|
||||
pub async fn select_network(&mut self, db: PatchDb, ssid: &Ssid) -> Result<bool, Error> {
|
||||
let m_id = self.check_active_network(ssid).await?;
|
||||
match m_id {
|
||||
None => Err(Error::new(
|
||||
@@ -734,7 +723,7 @@ impl WpaCli {
|
||||
}
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
pub async fn remove_network(&mut self, db: impl DbHandle, ssid: &Ssid) -> Result<bool, Error> {
|
||||
pub async fn remove_network(&mut self, db: PatchDb, ssid: &Ssid) -> Result<bool, Error> {
|
||||
let found_networks = self.find_networks(ssid).await?;
|
||||
if found_networks.is_empty() {
|
||||
return Ok(true);
|
||||
@@ -748,23 +737,16 @@ impl WpaCli {
|
||||
#[instrument(skip_all)]
|
||||
pub async fn set_add_network(
|
||||
&mut self,
|
||||
db: impl DbHandle,
|
||||
db: PatchDb,
|
||||
ssid: &Ssid,
|
||||
psk: &Psk,
|
||||
priority: isize,
|
||||
) -> Result<(), Error> {
|
||||
self.set_add_network_low(ssid, psk).await?;
|
||||
self.save_config(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
pub async fn add_network(
|
||||
&mut self,
|
||||
db: impl DbHandle,
|
||||
ssid: &Ssid,
|
||||
psk: &Psk,
|
||||
priority: isize,
|
||||
) -> Result<(), Error> {
|
||||
pub async fn add_network(&mut self, db: PatchDb, ssid: &Ssid, psk: &Psk) -> Result<(), Error> {
|
||||
self.add_network_low(ssid, psk).await?;
|
||||
self.save_config(db).await?;
|
||||
Ok(())
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::str::FromStr;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use color_eyre::eyre::eyre;
|
||||
use patch_db::{DbHandle, LockType};
|
||||
use rpc_toolkit::command;
|
||||
use sqlx::PgPool;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -12,6 +11,7 @@ use tracing::instrument;
|
||||
|
||||
use crate::backup::BackupReport;
|
||||
use crate::context::RpcContext;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::display_none;
|
||||
use crate::util::serde::display_serializable;
|
||||
@@ -30,13 +30,8 @@ pub async fn list(
|
||||
#[arg] limit: Option<u32>,
|
||||
) -> Result<Vec<Notification>, Error> {
|
||||
let limit = limit.unwrap_or(40);
|
||||
let mut handle = ctx.db.handle();
|
||||
match before {
|
||||
None => {
|
||||
let model = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.unread_notification_count();
|
||||
model.lock(&mut handle, LockType::Write).await?;
|
||||
let records = sqlx::query!(
|
||||
"SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT $1",
|
||||
limit as i64
|
||||
@@ -70,8 +65,14 @@ pub async fn list(
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<Notification>, Error>>()?;
|
||||
// set notification count to zero
|
||||
model.put(&mut handle, &0).await?;
|
||||
|
||||
ctx.db
|
||||
.mutate(|d| {
|
||||
d.as_server_info_mut()
|
||||
.as_unread_notification_count_mut()
|
||||
.ser(&0)
|
||||
})
|
||||
.await?;
|
||||
Ok(notifs)
|
||||
}
|
||||
Some(before) => {
|
||||
@@ -139,15 +140,7 @@ pub async fn create(
|
||||
#[arg] message: String,
|
||||
) -> Result<(), Error> {
|
||||
ctx.notification_manager
|
||||
.notify(
|
||||
&mut ctx.db.handle(),
|
||||
package,
|
||||
level,
|
||||
title,
|
||||
message,
|
||||
(),
|
||||
None,
|
||||
)
|
||||
.notify(ctx.db.clone(), package, level, title, message, (), None)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -232,10 +225,10 @@ impl NotificationManager {
|
||||
cache: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
pub async fn notify<Db: DbHandle, T: NotificationType>(
|
||||
#[instrument(skip(db, subtype, self))]
|
||||
pub async fn notify<T: NotificationType>(
|
||||
&self,
|
||||
db: &mut Db,
|
||||
db: PatchDb,
|
||||
package_id: Option<PackageId>,
|
||||
level: NotificationLevel,
|
||||
title: String,
|
||||
@@ -243,17 +236,14 @@ impl NotificationManager {
|
||||
subtype: T,
|
||||
debounce_interval: Option<u32>,
|
||||
) -> Result<(), Error> {
|
||||
let peek = db.peek().await?;
|
||||
if !self
|
||||
.should_notify(&package_id, &level, &title, debounce_interval)
|
||||
.await
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let mut count = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.unread_notification_count()
|
||||
.get_mut(db)
|
||||
.await?;
|
||||
let mut count = peek.as_server_info().as_unread_notification_count().de()?;
|
||||
let sql_package_id = package_id.as_ref().map(|p| &**p);
|
||||
let sql_code = T::CODE;
|
||||
let sql_level = format!("{}", level);
|
||||
@@ -268,9 +258,13 @@ impl NotificationManager {
|
||||
message,
|
||||
sql_data
|
||||
).execute(&self.sqlite).await?;
|
||||
*count += 1;
|
||||
count.save(db).await?;
|
||||
Ok(())
|
||||
count += 1;
|
||||
db.mutate(|db| {
|
||||
db.as_server_info_mut()
|
||||
.as_unread_notification_count_mut()
|
||||
.ser(&count)
|
||||
})
|
||||
.await
|
||||
}
|
||||
async fn should_notify(
|
||||
&self,
|
||||
|
||||
@@ -272,7 +272,7 @@ pub async fn execute(
|
||||
.invoke(crate::ErrorKind::OpenSsh)
|
||||
.await?;
|
||||
|
||||
let dev = MountGuard::mount(
|
||||
let embassy_fs = MountGuard::mount(
|
||||
&Bind::new(rootfs.as_ref()),
|
||||
current.join("media/embassy/embassyfs"),
|
||||
MountType::ReadOnly,
|
||||
@@ -315,19 +315,18 @@ pub async fn execute(
|
||||
.arg("update-grub2")
|
||||
.invoke(crate::ErrorKind::Grub)
|
||||
.await?;
|
||||
|
||||
dev.unmount(false).await?;
|
||||
if let Some(efivarfs) = efivarfs {
|
||||
efivarfs.unmount(false).await?;
|
||||
}
|
||||
sys.unmount(false).await?;
|
||||
proc.unmount(false).await?;
|
||||
embassy_fs.unmount(false).await?;
|
||||
if let Some(efi) = efi {
|
||||
efi.unmount(false).await?;
|
||||
}
|
||||
boot.unmount(false).await?;
|
||||
rootfs.unmount().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
6
backend/src/prelude.rs
Normal file
6
backend/src/prelude.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub use color_eyre::eyre::eyre;
|
||||
pub use models::OptionExt;
|
||||
|
||||
pub use crate::db::prelude::*;
|
||||
pub use crate::ensure_code;
|
||||
pub use crate::error::{Error, ErrorCollection, ErrorKind, ResultExt};
|
||||
@@ -12,7 +12,7 @@ use color_eyre::Report;
|
||||
use futures::future::{BoxFuture, Either as EitherFuture};
|
||||
use futures::{FutureExt, TryStreamExt};
|
||||
use helpers::{NonDetachingJoinHandle, UnixRpcClient};
|
||||
use models::{Id, ImageId};
|
||||
use models::{Id, ImageId, SYSTEM_PACKAGE_ID};
|
||||
use nix::sys::signal;
|
||||
use nix::unistd::Pid;
|
||||
use serde::de::DeserializeOwned;
|
||||
@@ -24,7 +24,8 @@ use tracing::instrument;
|
||||
|
||||
use super::ProcedureName;
|
||||
use crate::context::RpcContext;
|
||||
use crate::s9pk::manifest::{PackageId, SYSTEM_PACKAGE_ID};
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::docker::{remove_container, CONTAINER_TOOL};
|
||||
use crate::util::serde::{Duration as SerdeDuration, IoFormat};
|
||||
use crate::util::Version;
|
||||
@@ -44,8 +45,9 @@ lazy_static::lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, patch_db::HasModel)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct DockerContainers {
|
||||
pub main: DockerContainer,
|
||||
// #[serde(default)]
|
||||
@@ -57,6 +59,7 @@ pub struct DockerContainers {
|
||||
/// part of this struct by choice. Used for the times that we are creating our own entry points
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, patch_db::HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct DockerContainer {
|
||||
pub image: ImageId,
|
||||
#[serde(default)]
|
||||
@@ -228,7 +231,7 @@ impl DockerProcedure {
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<Result<O, (i32, String)>, Error> {
|
||||
let name = name.docker_name();
|
||||
let name: Option<&str> = name.as_ref().map(|x| &**x);
|
||||
let name: Option<&str> = name.as_deref();
|
||||
let mut cmd = tokio::process::Command::new(CONTAINER_TOOL);
|
||||
let container_name = Self::container_name(pkg_id, name);
|
||||
cmd.arg("run")
|
||||
@@ -383,14 +386,12 @@ impl DockerProcedure {
|
||||
&self,
|
||||
_ctx: &RpcContext,
|
||||
pkg_id: &PackageId,
|
||||
pkg_version: &Version,
|
||||
name: ProcedureName,
|
||||
volumes: &Volumes,
|
||||
_pkg_version: &Version,
|
||||
_name: ProcedureName,
|
||||
_volumes: &Volumes,
|
||||
input: Option<I>,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<Result<O, (i32, String)>, Error> {
|
||||
let name = name.docker_name();
|
||||
let name: Option<&str> = name.as_deref();
|
||||
let mut cmd = tokio::process::Command::new(CONTAINER_TOOL);
|
||||
|
||||
cmd.arg("exec");
|
||||
|
||||
@@ -10,6 +10,7 @@ use tracing::instrument;
|
||||
|
||||
use self::docker::DockerProcedure;
|
||||
use crate::context::RpcContext;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::Version;
|
||||
use crate::volume::Volumes;
|
||||
@@ -25,6 +26,7 @@ pub use models::ProcedureName;
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(tag = "type")]
|
||||
#[model = "Model<Self>"]
|
||||
pub enum PackageProcedure {
|
||||
Docker(DockerProcedure),
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ use serde_json::Value;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::prelude::*;
|
||||
use crate::procedure::ProcedureName;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::{Error, ErrorKind};
|
||||
|
||||
pub fn display_properties(response: Value, _: &ArgMatches) {
|
||||
@@ -20,17 +21,15 @@ pub async fn properties(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Res
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn fetch_properties(ctx: RpcContext, id: PackageId) -> Result<Value, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let peek = ctx.db.peek().await?;
|
||||
|
||||
let manifest: Manifest = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&id)
|
||||
.and_then(|p| p.installed())
|
||||
.map(|m| m.manifest())
|
||||
.get(&mut db)
|
||||
.await?
|
||||
.to_owned()
|
||||
.ok_or_else(|| Error::new(eyre!("{} is not installed", id), ErrorKind::NotFound))?;
|
||||
let manifest = peek
|
||||
.as_package_data()
|
||||
.as_idx(&id)
|
||||
.ok_or_else(|| Error::new(eyre!("{} is not installed", id), ErrorKind::NotFound))?
|
||||
.expect_as_installed()?
|
||||
.as_manifest()
|
||||
.de()?;
|
||||
if let Some(props) = manifest.properties {
|
||||
props
|
||||
.execute::<(), Value>(
|
||||
|
||||
@@ -2,8 +2,7 @@ use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
pub use models::{PackageId, SYSTEM_PACKAGE_ID};
|
||||
use patch_db::HasModel;
|
||||
pub use models::PackageId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
@@ -14,6 +13,7 @@ use crate::config::action::ConfigActions;
|
||||
use crate::dependencies::Dependencies;
|
||||
use crate::migration::Migrations;
|
||||
use crate::net::interface::Interfaces;
|
||||
use crate::prelude::*;
|
||||
use crate::procedure::docker::DockerContainers;
|
||||
use crate::procedure::PackageProcedure;
|
||||
use crate::status::health_check::HealthChecks;
|
||||
@@ -29,6 +29,7 @@ fn current_version() -> Version {
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct Manifest {
|
||||
#[serde(default = "current_version")]
|
||||
pub eos_version: Version,
|
||||
@@ -36,7 +37,6 @@ pub struct Manifest {
|
||||
#[serde(default)]
|
||||
pub git_hash: Option<GitHash>,
|
||||
pub title: String,
|
||||
#[model]
|
||||
pub version: Version,
|
||||
pub description: Description,
|
||||
#[serde(default)]
|
||||
@@ -52,31 +52,23 @@ pub struct Manifest {
|
||||
pub donation_url: Option<Url>,
|
||||
#[serde(default)]
|
||||
pub alerts: Alerts,
|
||||
#[model]
|
||||
pub main: PackageProcedure,
|
||||
pub health_checks: HealthChecks,
|
||||
#[model]
|
||||
pub config: Option<ConfigActions>,
|
||||
#[model]
|
||||
pub properties: Option<PackageProcedure>,
|
||||
#[model]
|
||||
pub volumes: Volumes,
|
||||
// #[serde(default)]
|
||||
pub interfaces: Interfaces,
|
||||
// #[serde(default)]
|
||||
#[model]
|
||||
pub backup: BackupActions,
|
||||
#[serde(default)]
|
||||
#[model]
|
||||
pub migrations: Migrations,
|
||||
#[serde(default)]
|
||||
pub actions: Actions,
|
||||
// #[serde(default)]
|
||||
// pub permissions: Permissions,
|
||||
#[serde(default)]
|
||||
#[model]
|
||||
pub dependencies: Dependencies,
|
||||
#[model]
|
||||
pub containers: Option<DockerContainers>,
|
||||
|
||||
#[serde(default)]
|
||||
|
||||
@@ -28,7 +28,7 @@ pub mod header;
|
||||
pub mod manifest;
|
||||
pub mod reader;
|
||||
|
||||
pub const SIG_CONTEXT: &'static [u8] = b"s9pk";
|
||||
pub const SIG_CONTEXT: &[u8] = b"s9pk";
|
||||
|
||||
#[command(cli_only, display(display_none))]
|
||||
#[instrument(skip_all)]
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::time::Duration;
|
||||
use color_eyre::eyre::eyre;
|
||||
use josekit::jwk::Jwk;
|
||||
use openssl::x509::X509;
|
||||
use patch_db::DbHandle;
|
||||
use rpc_toolkit::command;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -32,6 +31,7 @@ use crate::disk::REPAIR_DISK_PATH;
|
||||
use crate::hostname::Hostname;
|
||||
use crate::init::{init, InitResult};
|
||||
use crate::middleware::encrypt::EncryptedWire;
|
||||
use crate::prelude::*;
|
||||
use crate::util::io::{dir_copy, dir_size, Counter};
|
||||
use crate::{Error, ErrorKind, ResultExt};
|
||||
|
||||
@@ -57,23 +57,21 @@ async fn setup_init(
|
||||
let InitResult { secret_store, db } =
|
||||
init(&RpcContextConfig::load(ctx.config_path.clone()).await?).await?;
|
||||
let mut secrets_handle = secret_store.acquire().await?;
|
||||
let mut db_handle = db.handle();
|
||||
let mut secrets_tx = secrets_handle.begin().await?;
|
||||
let mut db_tx = db_handle.begin().await?;
|
||||
|
||||
let mut account = AccountInfo::load(&mut secrets_tx).await?;
|
||||
|
||||
if let Some(password) = password {
|
||||
account.set_password(&password)?;
|
||||
account.save(&mut secrets_tx).await?;
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.password_hash()
|
||||
.put(&mut db_tx, &account.password)
|
||||
.await?;
|
||||
db.mutate(|m| {
|
||||
m.as_server_info_mut()
|
||||
.as_password_hash_mut()
|
||||
.ser(&account.password)
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
db_tx.commit().await?;
|
||||
secrets_tx.commit().await?;
|
||||
|
||||
Ok((
|
||||
@@ -265,39 +263,47 @@ pub async fn execute(
|
||||
complete: false,
|
||||
}));
|
||||
drop(status);
|
||||
tokio::task::spawn(async move {
|
||||
match execute_inner(
|
||||
ctx.clone(),
|
||||
embassy_logicalname,
|
||||
embassy_password,
|
||||
recovery_source,
|
||||
recovery_password,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((guid, hostname, tor_addr, root_ca)) => {
|
||||
tracing::info!("Setup Complete!");
|
||||
*ctx.setup_result.write().await = Some((
|
||||
guid,
|
||||
SetupResult {
|
||||
tor_address: format!("https://{}", tor_addr),
|
||||
lan_address: hostname.lan_address(),
|
||||
root_ca: String::from_utf8(
|
||||
root_ca.to_pem().expect("failed to serialize root ca"),
|
||||
)
|
||||
.expect("invalid pem string"),
|
||||
},
|
||||
));
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: 0,
|
||||
total_bytes: None,
|
||||
complete: true,
|
||||
}));
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error Setting Up Server: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
*ctx.setup_status.write().await = Some(Err(e.into()));
|
||||
tokio::task::spawn({
|
||||
async move {
|
||||
let ctx = ctx.clone();
|
||||
let recovery_source = recovery_source;
|
||||
|
||||
let embassy_password = embassy_password;
|
||||
let recovery_source = recovery_source;
|
||||
let recovery_password = recovery_password;
|
||||
match execute_inner(
|
||||
ctx.clone(),
|
||||
embassy_logicalname,
|
||||
embassy_password,
|
||||
recovery_source,
|
||||
recovery_password,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((guid, hostname, tor_addr, root_ca)) => {
|
||||
tracing::info!("Setup Complete!");
|
||||
*ctx.setup_result.write().await = Some((
|
||||
guid,
|
||||
SetupResult {
|
||||
tor_address: format!("https://{}", tor_addr),
|
||||
lan_address: hostname.lan_address(),
|
||||
root_ca: String::from_utf8(
|
||||
root_ca.to_pem().expect("failed to serialize root ca"),
|
||||
)
|
||||
.expect("invalid pem string"),
|
||||
},
|
||||
));
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: 0,
|
||||
total_bytes: None,
|
||||
complete: true,
|
||||
}));
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error Setting Up Server: {}", e);
|
||||
tracing::debug!("{:?}", e);
|
||||
*ctx.setup_status.write().await = Some(Err(e.into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -396,7 +402,7 @@ async fn recover(
|
||||
) -> Result<(Arc<String>, Hostname, OnionAddressV3, X509), Error> {
|
||||
let recovery_source = TmpMountGuard::mount(&recovery_source, ReadWrite).await?;
|
||||
recover_full_embassy(
|
||||
ctx.clone(),
|
||||
ctx,
|
||||
guid.clone(),
|
||||
embassy_password,
|
||||
recovery_source,
|
||||
|
||||
@@ -15,7 +15,7 @@ lazy_static::lazy_static! {
|
||||
static ref C_0: f64 = *A_4 / SEMITONE_K.powf(9f64) / 2f64.powf(4f64);
|
||||
}
|
||||
|
||||
pub const SOUND_LOCK_FILE: &'static str = "/etc/embassy/sound.lock";
|
||||
pub const SOUND_LOCK_FILE: &str = "/etc/embassy/sound.lock";
|
||||
|
||||
struct SoundInterface {
|
||||
guard: Option<FileLock>,
|
||||
|
||||
@@ -103,7 +103,7 @@ impl HealthCheck {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[serde(tag = "result")]
|
||||
pub enum HealthCheckResult {
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use patch_db::{HasModel, Model};
|
||||
use models::PackageId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use self::health_check::HealthCheckId;
|
||||
use crate::dependencies::DependencyErrors;
|
||||
use crate::prelude::*;
|
||||
use crate::status::health_check::HealthCheckResult;
|
||||
|
||||
pub mod health_check;
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct Status {
|
||||
pub configured: bool,
|
||||
#[model]
|
||||
pub main: MainStatus,
|
||||
#[model]
|
||||
pub dependency_errors: DependencyErrors,
|
||||
#[serde(default)]
|
||||
pub dependency_errors: BTreeMap<(), ()>, // TODO: remove
|
||||
#[serde(default)]
|
||||
pub dependency_config_errors: DependencyConfigErrors,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, HasModel)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[model = "Model<Self>"]
|
||||
pub struct DependencyConfigErrors(pub BTreeMap<PackageId, String>);
|
||||
impl Map for DependencyConfigErrors {
|
||||
type Key = PackageId;
|
||||
type Value = String;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(tag = "status")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum MainStatus {
|
||||
@@ -83,8 +94,3 @@ impl MainStatus {
|
||||
MainStatus::BackingUp { started, health }
|
||||
}
|
||||
}
|
||||
impl MainStatusModel {
|
||||
pub fn started(self) -> Model<Option<DateTime<Utc>>> {
|
||||
self.0.child("started")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::logs::{
|
||||
cli_logs_generic_follow, cli_logs_generic_nofollow, fetch_logs, follow_logs, LogFollowResponse,
|
||||
LogResponse, LogSource,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::shutdown::Shutdown;
|
||||
use crate::util::serde::{display_serializable, IoFormat};
|
||||
use crate::util::{display_none, Invoke};
|
||||
@@ -59,16 +60,12 @@ pub async fn enable_zram() -> Result<(), Error> {
|
||||
|
||||
#[command(display(display_none))]
|
||||
pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(), Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut zram = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.zram()
|
||||
.get_mut(&mut db)
|
||||
.await?;
|
||||
if enable == *zram {
|
||||
let db = ctx.db.peek().await?;
|
||||
|
||||
let zram = db.as_server_info().as_zram().de()?;
|
||||
if enable == zram {
|
||||
return Ok(());
|
||||
}
|
||||
*zram = enable;
|
||||
if enable {
|
||||
enable_zram().await?;
|
||||
} else {
|
||||
@@ -80,7 +77,12 @@ pub async fn zram(#[context] ctx: RpcContext, #[arg] enable: bool) -> Result<(),
|
||||
.await
|
||||
.with_kind(ErrorKind::Zram)?;
|
||||
}
|
||||
zram.save(&mut db).await?;
|
||||
ctx.db
|
||||
.mutate(|v| {
|
||||
v.as_server_info_mut().as_zram_mut().ser(&enable)?;
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use emver::Version;
|
||||
use helpers::{Rsync, RsyncOptions};
|
||||
use lazy_static::lazy_static;
|
||||
use patch_db::{DbHandle, LockType, Revision};
|
||||
use reqwest::Url;
|
||||
use rpc_toolkit::command;
|
||||
use tokio::process::Command;
|
||||
@@ -21,6 +19,7 @@ use crate::disk::mount::filesystem::ReadWrite;
|
||||
use crate::disk::mount::guard::MountGuard;
|
||||
use crate::marketplace::with_query_params;
|
||||
use crate::notifications::NotificationLevel;
|
||||
use crate::prelude::*;
|
||||
use crate::sound::{
|
||||
CIRCLE_OF_5THS_SHORT, UPDATE_FAILED_1, UPDATE_FAILED_2, UPDATE_FAILED_3, UPDATE_FAILED_4,
|
||||
};
|
||||
@@ -76,15 +75,12 @@ fn display_update_result(status: UpdateResult, _: &ArgMatches) {
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn maybe_do_update(
|
||||
ctx: RpcContext,
|
||||
marketplace_url: Url,
|
||||
) -> Result<Option<Arc<Revision>>, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
async fn maybe_do_update(ctx: RpcContext, marketplace_url: Url) -> Result<Option<()>, Error> {
|
||||
let peeked = ctx.db.peek().await?;
|
||||
let latest_version: Version = ctx
|
||||
.client
|
||||
.get(with_query_params(
|
||||
&ctx,
|
||||
ctx.clone(),
|
||||
format!("{}/eos/v0/latest", marketplace_url,).parse()?,
|
||||
))
|
||||
.send()
|
||||
@@ -94,31 +90,8 @@ async fn maybe_do_update(
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?
|
||||
.version;
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.lock(&mut db, LockType::Write)
|
||||
.await?;
|
||||
let current_version = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.version()
|
||||
.get_mut(&mut db)
|
||||
.await?;
|
||||
if &latest_version < ¤t_version {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut tx = db.begin().await?;
|
||||
let mut status = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.status_info()
|
||||
.get_mut(&mut tx)
|
||||
.await?;
|
||||
if status.update_progress.is_some() {
|
||||
return Err(Error::new(
|
||||
eyre!("Server is already updating!"),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
if status.updated {
|
||||
let current_version = peeked.as_server_info().as_version().de()?;
|
||||
if latest_version < *current_version {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@@ -126,38 +99,59 @@ async fn maybe_do_update(
|
||||
base: marketplace_url,
|
||||
version: latest_version,
|
||||
};
|
||||
let status = ctx
|
||||
.db
|
||||
.mutate(|db| {
|
||||
let mut status = peeked.as_server_info().as_status_info().de()?;
|
||||
if status.update_progress.is_some() {
|
||||
return Err(Error::new(
|
||||
eyre!("Server is already updating!"),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
|
||||
status.update_progress = Some(UpdateProgress {
|
||||
size: None,
|
||||
downloaded: 0,
|
||||
});
|
||||
status.save(&mut tx).await?;
|
||||
let rev = tx.commit().await?;
|
||||
status.update_progress = Some(UpdateProgress {
|
||||
size: None,
|
||||
downloaded: 0,
|
||||
});
|
||||
db.as_server_info_mut().as_status_info_mut().ser(&status)?;
|
||||
Ok(status)
|
||||
})
|
||||
.await?;
|
||||
|
||||
if status.updated {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
let res = do_update(ctx.clone(), eos_url).await;
|
||||
let mut db = ctx.db.handle();
|
||||
let mut status = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.status_info()
|
||||
.get_mut(&mut db)
|
||||
.await
|
||||
.expect("could not access status");
|
||||
status.update_progress = None;
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_server_info_mut()
|
||||
.as_status_info_mut()
|
||||
.as_update_progress_mut()
|
||||
.ser(&None)
|
||||
})
|
||||
.await?;
|
||||
match res {
|
||||
Ok(()) => {
|
||||
status.updated = true;
|
||||
status.save(&mut db).await.expect("could not save status");
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_server_info_mut()
|
||||
.as_status_info_mut()
|
||||
.as_updated_mut()
|
||||
.ser(&true)
|
||||
})
|
||||
.await?;
|
||||
CIRCLE_OF_5THS_SHORT
|
||||
.play()
|
||||
.await
|
||||
.expect("could not play sound");
|
||||
}
|
||||
Err(e) => {
|
||||
status.save(&mut db).await.expect("could not save status");
|
||||
ctx.notification_manager
|
||||
.notify(
|
||||
&mut db,
|
||||
ctx.db.clone(),
|
||||
None,
|
||||
NotificationLevel::Error,
|
||||
"embassyOS Update Failed".to_owned(),
|
||||
@@ -186,8 +180,9 @@ async fn maybe_do_update(
|
||||
.expect("could not play song: update failed 4");
|
||||
}
|
||||
}
|
||||
Ok::<(), Error>(())
|
||||
});
|
||||
Ok(rev)
|
||||
Ok(Some(()))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
@@ -199,17 +194,16 @@ async fn do_update(ctx: RpcContext, eos_url: EosUrl) -> Result<(), Error> {
|
||||
)
|
||||
.await?;
|
||||
while let Some(progress) = rsync.progress.next().await {
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.status_info()
|
||||
.update_progress()
|
||||
.put(
|
||||
&mut ctx.db.handle(),
|
||||
&UpdateProgress {
|
||||
size: Some(100),
|
||||
downloaded: (100.0 * progress) as u64,
|
||||
},
|
||||
)
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_server_info_mut()
|
||||
.as_status_info_mut()
|
||||
.as_update_progress_mut()
|
||||
.ser(&Some(UpdateProgress {
|
||||
size: Some(100),
|
||||
downloaded: (100.0 * progress) as u64,
|
||||
}))
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
rsync.wait().await?;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use patch_db::Value;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::util::serde::IoFormat;
|
||||
use crate::{Config, Error, ResultExt};
|
||||
use crate::{Config, Error};
|
||||
|
||||
pub const DEVICE_CONFIG_PATH: &str = "/media/embassy/config/config.yaml";
|
||||
pub const CONFIG_PATH: &str = "/etc/embassy/config.yaml";
|
||||
@@ -37,7 +38,7 @@ pub fn load_config_from_paths<'a, T: for<'de> Deserialize<'de>>(
|
||||
config = merge_configs(config, new);
|
||||
}
|
||||
}
|
||||
serde_json::from_value(Value::Object(config)).with_kind(crate::ErrorKind::Deserialization)
|
||||
from_value(Value::Object(config))
|
||||
}
|
||||
|
||||
pub fn merge_configs(mut first: Config, second: Config) -> Config {
|
||||
|
||||
@@ -376,5 +376,5 @@ async fn s9pk_test() {
|
||||
.unwrap();
|
||||
|
||||
let manifest = s9pk.manifest().await.unwrap();
|
||||
assert_eq!(&**manifest.id, "ghost");
|
||||
assert_eq!(&manifest.id.to_string(), "ghost");
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ pub mod logger;
|
||||
pub mod lshw;
|
||||
pub mod serde;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, ::serde::Deserialize, ::serde::Serialize)]
|
||||
pub enum Never {}
|
||||
impl Never {}
|
||||
impl Never {
|
||||
@@ -182,7 +182,7 @@ impl<T> Container<T> {
|
||||
std::mem::replace(&mut *self.0.write().await, Some(value))
|
||||
}
|
||||
pub async fn take(&self) -> Option<T> {
|
||||
std::mem::replace(&mut *self.0.write().await, None)
|
||||
self.0.write().await.take()
|
||||
}
|
||||
pub async fn is_empty(&self) -> bool {
|
||||
self.0.read().await.is_none()
|
||||
@@ -219,7 +219,7 @@ impl<H: Digest, W: tokio::io::AsyncWrite> tokio::io::AsyncWrite for HashWriter<H
|
||||
buf: &[u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
let this = self.project();
|
||||
let written = tokio::io::AsyncWrite::poll_write(this.writer, cx, &buf);
|
||||
let written = tokio::io::AsyncWrite::poll_write(this.writer, cx, buf);
|
||||
match written {
|
||||
// only update the hasher once
|
||||
Poll::Ready(res) => {
|
||||
|
||||
@@ -2,23 +2,12 @@ use std::cmp::Ordering;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use color_eyre::eyre::eyre;
|
||||
use patch_db::DbHandle;
|
||||
use rpc_toolkit::command;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::init::InitReceipts;
|
||||
use crate::prelude::*;
|
||||
use crate::Error;
|
||||
|
||||
mod v0_3_0;
|
||||
mod v0_3_0_1;
|
||||
mod v0_3_0_2;
|
||||
mod v0_3_0_3;
|
||||
mod v0_3_1;
|
||||
mod v0_3_1_1;
|
||||
mod v0_3_1_2;
|
||||
mod v0_3_2;
|
||||
mod v0_3_2_1;
|
||||
mod v0_3_3;
|
||||
mod v0_3_4;
|
||||
mod v0_3_4_1;
|
||||
mod v0_3_4_2;
|
||||
@@ -30,16 +19,6 @@ pub type Current = v0_3_4_4::Version;
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
enum Version {
|
||||
V0_3_0(Wrapper<v0_3_0::Version>),
|
||||
V0_3_0_1(Wrapper<v0_3_0_1::Version>),
|
||||
V0_3_0_2(Wrapper<v0_3_0_2::Version>),
|
||||
V0_3_0_3(Wrapper<v0_3_0_3::Version>),
|
||||
V0_3_1(Wrapper<v0_3_1::Version>),
|
||||
V0_3_1_1(Wrapper<v0_3_1_1::Version>),
|
||||
V0_3_1_2(Wrapper<v0_3_1_2::Version>),
|
||||
V0_3_2(Wrapper<v0_3_2::Version>),
|
||||
V0_3_2_1(Wrapper<v0_3_2_1::Version>),
|
||||
V0_3_3(Wrapper<v0_3_3::Version>),
|
||||
V0_3_4(Wrapper<v0_3_4::Version>),
|
||||
V0_3_4_1(Wrapper<v0_3_4_1::Version>),
|
||||
V0_3_4_2(Wrapper<v0_3_4_2::Version>),
|
||||
@@ -60,16 +39,6 @@ impl Version {
|
||||
#[cfg(test)]
|
||||
fn as_sem_ver(&self) -> emver::Version {
|
||||
match self {
|
||||
Version::V0_3_0(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_0_1(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_0_2(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_0_3(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_1(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_1_1(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_1_2(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_2(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_2_1(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_3(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_4(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_4_1(Wrapper(x)) => x.semver(),
|
||||
Version::V0_3_4_2(Wrapper(x)) => x.semver(),
|
||||
@@ -89,55 +58,43 @@ where
|
||||
fn new() -> Self;
|
||||
fn semver(&self) -> emver::Version;
|
||||
fn compat(&self) -> &'static emver::VersionRange;
|
||||
async fn up<Db: DbHandle>(&self, db: &mut Db, secrets: &PgPool) -> Result<(), Error>;
|
||||
async fn down<Db: DbHandle>(&self, db: &mut Db, secrets: &PgPool) -> Result<(), Error>;
|
||||
async fn commit<Db: DbHandle>(
|
||||
&self,
|
||||
db: &mut Db,
|
||||
receipts: &InitReceipts,
|
||||
) -> Result<(), Error> {
|
||||
receipts
|
||||
.version_range
|
||||
.set(db, self.compat().clone())
|
||||
.await?;
|
||||
receipts
|
||||
.server_version
|
||||
.set(db, self.semver().into())
|
||||
.await?;
|
||||
|
||||
async fn up(&self, db: PatchDb, secrets: &PgPool) -> Result<(), Error>;
|
||||
async fn down(&self, db: PatchDb, secrets: &PgPool) -> Result<(), Error>;
|
||||
async fn commit(&self, db: PatchDb) -> Result<(), Error> {
|
||||
let semver = self.semver().into();
|
||||
let compat = self.compat().clone();
|
||||
db.mutate(|d| {
|
||||
d.as_server_info_mut().as_version_mut().ser(&semver)?;
|
||||
d.as_server_info_mut()
|
||||
.as_eos_version_compat_mut()
|
||||
.ser(&compat)?;
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn migrate_to<V: VersionT, Db: DbHandle>(
|
||||
async fn migrate_to<V: VersionT>(
|
||||
&self,
|
||||
version: &V,
|
||||
db: &mut Db,
|
||||
db: PatchDb,
|
||||
secrets: &PgPool,
|
||||
receipts: &InitReceipts,
|
||||
) -> Result<(), Error> {
|
||||
match self.semver().cmp(&version.semver()) {
|
||||
Ordering::Greater => {
|
||||
self.rollback_to_unchecked(version, db, secrets, receipts)
|
||||
.await
|
||||
}
|
||||
Ordering::Less => {
|
||||
version
|
||||
.migrate_from_unchecked(self, db, secrets, receipts)
|
||||
.await
|
||||
}
|
||||
Ordering::Greater => self.rollback_to_unchecked(version, db, secrets).await,
|
||||
Ordering::Less => version.migrate_from_unchecked(self, db, secrets).await,
|
||||
Ordering::Equal => Ok(()),
|
||||
}
|
||||
}
|
||||
async fn migrate_from_unchecked<V: VersionT, Db: DbHandle>(
|
||||
async fn migrate_from_unchecked<V: VersionT>(
|
||||
&self,
|
||||
version: &V,
|
||||
db: &mut Db,
|
||||
db: PatchDb,
|
||||
secrets: &PgPool,
|
||||
receipts: &InitReceipts,
|
||||
) -> Result<(), Error> {
|
||||
let previous = Self::Previous::new();
|
||||
if version.semver() < previous.semver() {
|
||||
previous
|
||||
.migrate_from_unchecked(version, db, secrets, receipts)
|
||||
.migrate_from_unchecked(version, db.clone(), secrets)
|
||||
.await?;
|
||||
} else if version.semver() > previous.semver() {
|
||||
return Err(Error::new(
|
||||
@@ -149,25 +106,22 @@ where
|
||||
));
|
||||
}
|
||||
tracing::info!("{} -> {}", previous.semver(), self.semver(),);
|
||||
self.up(db, secrets).await?;
|
||||
self.commit(db, receipts).await?;
|
||||
self.up(db.clone(), secrets).await?;
|
||||
self.commit(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn rollback_to_unchecked<V: VersionT, Db: DbHandle>(
|
||||
async fn rollback_to_unchecked<V: VersionT>(
|
||||
&self,
|
||||
version: &V,
|
||||
db: &mut Db,
|
||||
db: PatchDb,
|
||||
secrets: &PgPool,
|
||||
receipts: &InitReceipts,
|
||||
) -> Result<(), Error> {
|
||||
let previous = Self::Previous::new();
|
||||
tracing::info!("{} -> {}", self.semver(), previous.semver(),);
|
||||
self.down(db, secrets).await?;
|
||||
previous.commit(db, receipts).await?;
|
||||
self.down(db.clone(), secrets).await?;
|
||||
previous.commit(db.clone()).await?;
|
||||
if version.semver() < previous.semver() {
|
||||
previous
|
||||
.rollback_to_unchecked(version, db, secrets, receipts)
|
||||
.await?;
|
||||
previous.rollback_to_unchecked(version, db, secrets).await?;
|
||||
} else if version.semver() > previous.semver() {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
@@ -205,73 +159,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init<Db: DbHandle>(
|
||||
db: &mut Db,
|
||||
secrets: &PgPool,
|
||||
receipts: &crate::init::InitReceipts,
|
||||
) -> Result<(), Error> {
|
||||
let version = Version::from_util_version(receipts.server_version.get(db).await?);
|
||||
pub async fn init(db: &PatchDb, secrets: &PgPool) -> Result<(), Error> {
|
||||
let version = Version::from_util_version(db.peek().await?.as_server_info().as_version().de()?);
|
||||
|
||||
match version {
|
||||
Version::V0_3_0(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_0_1(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_0_2(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_0_3(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_1(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_1_1(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_1_2(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_2(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_2_1(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_3(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_4(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_4_1(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_4_2(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_4_3(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_4_4(v) => {
|
||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||
.await?
|
||||
}
|
||||
Version::V0_3_4(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?,
|
||||
Version::V0_3_4_1(v) => v.0.migrate_to(&Current::new(), db.clone(), secrets).await?,
|
||||
Version::V0_3_4_2(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::Other(_) => {
|
||||
return Err(Error::new(
|
||||
eyre!("Cannot downgrade"),
|
||||
@@ -304,16 +200,6 @@ mod tests {
|
||||
|
||||
fn versions() -> impl Strategy<Value = Version> {
|
||||
prop_oneof![
|
||||
Just(Version::V0_3_0(Wrapper(v0_3_0::Version::new()))),
|
||||
Just(Version::V0_3_0_1(Wrapper(v0_3_0_1::Version::new()))),
|
||||
Just(Version::V0_3_0_2(Wrapper(v0_3_0_2::Version::new()))),
|
||||
Just(Version::V0_3_0_3(Wrapper(v0_3_0_3::Version::new()))),
|
||||
Just(Version::V0_3_1(Wrapper(v0_3_1::Version::new()))),
|
||||
Just(Version::V0_3_1_1(Wrapper(v0_3_1_1::Version::new()))),
|
||||
Just(Version::V0_3_1_2(Wrapper(v0_3_1_2::Version::new()))),
|
||||
Just(Version::V0_3_2(Wrapper(v0_3_2::Version::new()))),
|
||||
Just(Version::V0_3_2_1(Wrapper(v0_3_2_1::Version::new()))),
|
||||
Just(Version::V0_3_3(Wrapper(v0_3_3::Version::new()))),
|
||||
Just(Version::V0_3_4(Wrapper(v0_3_4::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()))),
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
use emver::VersionRange;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use super::*;
|
||||
|
||||
const V0_3_0: emver::Version = emver::Version::new(0, 3, 0, 0);
|
||||
lazy_static! {
|
||||
pub static ref V0_3_0_COMPAT: VersionRange = VersionRange::Conj(
|
||||
Box::new(VersionRange::Anchor(
|
||||
emver::GTE,
|
||||
emver::Version::new(0, 3, 0, 0),
|
||||
)),
|
||||
Box::new(VersionRange::Anchor(emver::LTE, Current::new().semver())),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_0::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_0
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::*;
|
||||
|
||||
const V0_3_0_1: emver::Version = emver::Version::new(0, 3, 0, 1);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_0::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_0_1
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*v0_3_0::V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::*;
|
||||
|
||||
const V0_3_0_2: emver::Version = emver::Version::new(0, 3, 0, 2);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_0_1::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_0_2
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*v0_3_0::V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::*;
|
||||
|
||||
const V0_3_0_3: emver::Version = emver::Version::new(0, 3, 0, 3);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_0_2::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_0_3
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*v0_3_0::V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
|
||||
const V0_3_1: emver::Version = emver::Version::new(0, 3, 1, 0);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_0_3::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_1
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
|
||||
const V0_3_1_1: emver::Version = emver::Version::new(0, 3, 1, 1);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_1::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_1_1
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
|
||||
const V0_3_1_2: emver::Version = emver::Version::new(0, 3, 1, 2);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_1_1::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_1_2
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
|
||||
const V0_3_2: emver::Version = emver::Version::new(0, 3, 2, 0);
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref DEFAULT_UI: serde_json::Value =serde_json::json!({
|
||||
"name": null,
|
||||
"auto-check-updates": true,
|
||||
"pkg-order": [],
|
||||
"ack-welcome": "0.3.2",
|
||||
"marketplace": {
|
||||
"selected-id": null,
|
||||
"known-hosts": {}
|
||||
},
|
||||
"dev": {},
|
||||
"gaming": {
|
||||
"snake": {
|
||||
"high-score": 0
|
||||
}
|
||||
},
|
||||
"ack-instructions": {}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_1_2::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_2
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
let hostname = legacy::hostname::get_hostname(db).await?;
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.hostname()
|
||||
.put(db, &Some(hostname.0))
|
||||
.await?;
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.id()
|
||||
.put(db, &legacy::hostname::generate_id())
|
||||
.await?;
|
||||
|
||||
legacy::hostname::sync_hostname(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
mod legacy {
|
||||
pub mod hostname {
|
||||
use patch_db::DbHandle;
|
||||
use rand::{thread_rng, Rng};
|
||||
use tokio::process::Command;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::util::Invoke;
|
||||
use crate::{Error, ErrorKind};
|
||||
#[derive(Clone, serde::Deserialize, serde::Serialize, Debug)]
|
||||
pub struct Hostname(pub String);
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref ADJECTIVES: Vec<String> = include_str!("../assets/adjectives.txt").lines().map(|x| x.to_string()).collect();
|
||||
static ref NOUNS: Vec<String> = include_str!("../assets/nouns.txt").lines().map(|x| x.to_string()).collect();
|
||||
}
|
||||
impl AsRef<str> for Hostname {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_hostname() -> Hostname {
|
||||
let mut rng = thread_rng();
|
||||
let adjective = &ADJECTIVES[rng.gen_range(0..ADJECTIVES.len())];
|
||||
let noun = &NOUNS[rng.gen_range(0..NOUNS.len())];
|
||||
Hostname(format!("embassy-{adjective}-{noun}"))
|
||||
}
|
||||
|
||||
pub fn generate_id() -> String {
|
||||
let id = uuid::Uuid::new_v4();
|
||||
id.to_string()
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get_current_hostname() -> Result<Hostname, Error> {
|
||||
let out = Command::new("hostname")
|
||||
.invoke(ErrorKind::ParseSysInfo)
|
||||
.await?;
|
||||
let out_string = String::from_utf8(out)?;
|
||||
Ok(Hostname(out_string.trim().to_owned()))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn set_hostname(hostname: &Hostname) -> Result<(), Error> {
|
||||
let hostname: &String = &hostname.0;
|
||||
let _out = Command::new("hostnamectl")
|
||||
.arg("set-hostname")
|
||||
.arg(hostname)
|
||||
.invoke(ErrorKind::ParseSysInfo)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn get_id<Db: DbHandle>(handle: &mut Db) -> Result<String, Error> {
|
||||
let id = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.id()
|
||||
.get(handle)
|
||||
.await?;
|
||||
Ok(id.to_string())
|
||||
}
|
||||
|
||||
pub async fn get_hostname<Db: DbHandle>(handle: &mut Db) -> Result<Hostname, Error> {
|
||||
if let Ok(hostname) = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.hostname()
|
||||
.get(handle)
|
||||
.await
|
||||
{
|
||||
if let Some(hostname) = hostname.to_owned() {
|
||||
return Ok(Hostname(hostname));
|
||||
}
|
||||
}
|
||||
let id = get_id(handle).await?;
|
||||
if id.len() != 8 {
|
||||
return Ok(generate_hostname());
|
||||
}
|
||||
return Ok(Hostname(format!("embassy-{}", id)));
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
pub async fn sync_hostname<Db: DbHandle>(handle: &mut Db) -> Result<(), Error> {
|
||||
set_hostname(&get_hostname(handle).await?).await?;
|
||||
Command::new("systemctl")
|
||||
.arg("restart")
|
||||
.arg("avahi-daemon")
|
||||
.invoke(crate::ErrorKind::Network)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
|
||||
const V0_3_2_1: emver::Version = emver::Version::new(0, 3, 2, 1);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_2::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_2_1
|
||||
}
|
||||
fn compat(&self) -> &'static emver::VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use emver::VersionRange;
|
||||
use regex::Regex;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
use crate::DEFAULT_MARKETPLACE;
|
||||
|
||||
const V0_3_3: emver::Version = emver::Version::new(0, 3, 3, 0);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Version;
|
||||
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_2_1::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> emver::Version {
|
||||
V0_3_3
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
let mut ui = crate::db::DatabaseModel::new().ui().get_mut(db).await?;
|
||||
|
||||
if let Some(Value::String(selected_url)) =
|
||||
ui["marketplace"]
|
||||
.get("selected-id")
|
||||
.and_then(|selected_id| {
|
||||
if let Value::String(selected_id) = selected_id {
|
||||
return Some(ui["marketplace"]["known-hosts"].get(&selected_id)?);
|
||||
}
|
||||
None
|
||||
})
|
||||
{
|
||||
ui["marketplace"]["selected-url"] = json!(selected_url);
|
||||
}
|
||||
if let Value::Object(ref mut obj) = *ui {
|
||||
obj.remove("pkg-order");
|
||||
obj.remove("auto-check-updates");
|
||||
}
|
||||
let known_hosts = ui["marketplace"]["known-hosts"].take();
|
||||
ui["marketplace"]["known-hosts"] = json!({});
|
||||
if let Value::Object(known_hosts) = known_hosts {
|
||||
for (_id, value) in known_hosts {
|
||||
if let Value::String(url) = &value["url"] {
|
||||
ui["marketplace"]["known-hosts"][ensure_trailing_slashes(url)] = json!({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui["marketplace"]["known-hosts"]["https://registry.start9.com/"] = json!({});
|
||||
|
||||
if let Some(Value::Object(ref mut obj)) = ui.get_mut("marketplace") {
|
||||
obj.remove("selected-id");
|
||||
}
|
||||
if ui["marketplace"]["selected-url"].is_null() {
|
||||
ui["marketplace"]["selected-url"] = json!(MarketPlaceUrls::Default.url());
|
||||
}
|
||||
ui.save(db).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
let mut ui = crate::db::DatabaseModel::new().ui().get_mut(db).await?;
|
||||
let selected_url = ui["marketplace"]["selected-url"]
|
||||
.as_str()
|
||||
.map(|x| x.to_owned());
|
||||
let known_hosts = ui["marketplace"]["known-hosts"].take();
|
||||
ui["marketplace"]["known-hosts"] = json!({});
|
||||
if let Value::Object(known_hosts) = known_hosts {
|
||||
for (url, obj) in known_hosts {
|
||||
if let Value::String(name) = &obj["name"] {
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
if Some(name) == selected_url.as_ref() {
|
||||
ui["marketplace"]["selected-id"] = Value::String(id.clone());
|
||||
}
|
||||
ui["marketplace"]["known-hosts"][id.as_str()] = json!({
|
||||
"name": name,
|
||||
"url": url
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ui["auto-check-updates"] = Value::Bool(true);
|
||||
ui["pkg-order"] = json!(crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.keys(db)
|
||||
.await?
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>());
|
||||
if let Some(Value::Object(ref mut obj)) = ui.get_mut("marketplace") {
|
||||
obj.remove("selected-url");
|
||||
}
|
||||
ui.save(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_trailing_slashes(url: &str) -> String {
|
||||
lazy_static::lazy_static! {
|
||||
static ref REG: Regex = Regex::new(r".*/$").unwrap();
|
||||
}
|
||||
if REG.is_match(url) {
|
||||
return url.to_string();
|
||||
}
|
||||
format!("{url}/")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ensure_trailing_slashed() {
|
||||
assert_eq!(
|
||||
&ensure_trailing_slashes("http://start9.com"),
|
||||
"http://start9.com/"
|
||||
);
|
||||
assert_eq!(
|
||||
&ensure_trailing_slashes("http://start9.com/"),
|
||||
"http://start9.com/"
|
||||
);
|
||||
assert_eq!(
|
||||
&ensure_trailing_slashes("http://start9.com/a"),
|
||||
"http://start9.com/a/"
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum MarketPlaceUrls {
|
||||
Default,
|
||||
}
|
||||
|
||||
impl MarketPlaceUrls {
|
||||
pub fn url(&self) -> String {
|
||||
let url_string = match self {
|
||||
MarketPlaceUrls::Default => DEFAULT_MARKETPLACE,
|
||||
};
|
||||
format!("{url_string}/")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_that_ui_includes_url() {
|
||||
let ui: Value =
|
||||
serde_json::from_str(include_str!("../../../frontend/patchdb-ui-seed.json")).unwrap();
|
||||
for market_place in [MarketPlaceUrls::Default] {
|
||||
let url = market_place.url();
|
||||
assert!(
|
||||
!ui["marketplace"]["known-hosts"][&url].is_null(),
|
||||
"Should have a market place for {url}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,26 @@ use async_trait::async_trait;
|
||||
use emver::VersionRange;
|
||||
use itertools::Itertools;
|
||||
use openssl::hash::MessageDigest;
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::json;
|
||||
use ssh_key::public::Ed25519PublicKey;
|
||||
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
use crate::account::AccountInfo;
|
||||
use crate::hostname::{generate_hostname, sync_hostname, Hostname};
|
||||
use crate::hostname::{sync_hostname, Hostname};
|
||||
use crate::prelude::*;
|
||||
|
||||
const V0_3_4: emver::Version = emver::Version::new(0, 3, 4, 0);
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref V0_3_0_COMPAT: VersionRange = VersionRange::Conj(
|
||||
Box::new(VersionRange::Anchor(
|
||||
emver::GTE,
|
||||
emver::Version::new(0, 3, 0, 0),
|
||||
)),
|
||||
Box::new(VersionRange::Anchor(emver::LTE, Current::new().semver())),
|
||||
);
|
||||
}
|
||||
|
||||
const COMMUNITY_URL: &str = "https://community-registry.start9.com/";
|
||||
const MAIN_REGISTRY: &str = "https://registry.start9.com/";
|
||||
const COMMUNITY_SERVICES: &[&str] = &[
|
||||
@@ -32,7 +42,7 @@ pub struct Version;
|
||||
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_3_3::Version;
|
||||
type Previous = Self;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
@@ -42,105 +52,84 @@ impl VersionT for Version {
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, db: &mut Db, secrets: &PgPool) -> Result<(), Error> {
|
||||
async fn up(&self, db: PatchDb, secrets: &PgPool) -> Result<(), Error> {
|
||||
let mut account = AccountInfo::load(secrets).await?;
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.pubkey()
|
||||
.put(
|
||||
db,
|
||||
&ssh_key::PublicKey::from(Ed25519PublicKey::from(&account.key.ssh_key()))
|
||||
.to_openssh()?,
|
||||
)
|
||||
let account = db
|
||||
.mutate(|d| {
|
||||
d.as_server_info_mut().as_pubkey_mut().ser(
|
||||
&ssh_key::PublicKey::from(Ed25519PublicKey::from(&account.key.ssh_key()))
|
||||
.to_openssh()?,
|
||||
)?;
|
||||
d.as_server_info_mut().as_ca_fingerprint_mut().ser(
|
||||
&account
|
||||
.root_ca_cert
|
||||
.digest(MessageDigest::sha256())
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|x| format!("{x:X}"))
|
||||
.join(":"),
|
||||
)?;
|
||||
let server_info = d.as_server_info();
|
||||
account.hostname = server_info.as_hostname().de().map(Hostname)?;
|
||||
account.server_id = server_info.as_id().de()?;
|
||||
|
||||
Ok(account)
|
||||
})
|
||||
.await?;
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.ca_fingerprint()
|
||||
.put(
|
||||
db,
|
||||
&account
|
||||
.root_ca_cert
|
||||
.digest(MessageDigest::sha256())
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|x| format!("{x:X}"))
|
||||
.join(":"),
|
||||
)
|
||||
.await?;
|
||||
let server_info = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.get(db)
|
||||
.await?
|
||||
.into_owned();
|
||||
account.hostname = server_info
|
||||
.hostname
|
||||
.map(Hostname)
|
||||
.unwrap_or_else(generate_hostname);
|
||||
account.server_id = server_info.id;
|
||||
account.save(secrets).await?;
|
||||
sync_hostname(&account.hostname).await?;
|
||||
|
||||
let parsed_url = Some(COMMUNITY_URL.parse().unwrap());
|
||||
let mut ui = crate::db::DatabaseModel::new().ui().get_mut(db).await?;
|
||||
ui["marketplace"]["known-hosts"][COMMUNITY_URL] = json!({});
|
||||
ui["marketplace"]["known-hosts"][MAIN_REGISTRY] = json!({});
|
||||
for package_id in crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.keys(db)
|
||||
.await?
|
||||
{
|
||||
if !COMMUNITY_SERVICES.contains(&&*package_id.to_string()) {
|
||||
continue;
|
||||
db.mutate(|d| {
|
||||
let mut ui = d.as_ui().de()?;
|
||||
use imbl_value::json;
|
||||
ui["marketplace"]["known-hosts"][COMMUNITY_URL] = json!({});
|
||||
ui["marketplace"]["known-hosts"][MAIN_REGISTRY] = json!({});
|
||||
for package_id in d.as_package_data().keys()? {
|
||||
if !COMMUNITY_SERVICES.contains(&&*package_id.to_string()) {
|
||||
continue;
|
||||
}
|
||||
d.as_package_data_mut()
|
||||
.as_idx_mut(&package_id)
|
||||
.or_not_found(&package_id)?
|
||||
.as_installed_mut()
|
||||
.or_not_found(&package_id)?
|
||||
.as_marketplace_url_mut()
|
||||
.ser(&parsed_url)?;
|
||||
}
|
||||
crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&package_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?
|
||||
.marketplace_url()
|
||||
.put(db, &parsed_url)
|
||||
.await?;
|
||||
}
|
||||
ui["theme"] = json!("Dark".to_string());
|
||||
ui["widgets"] = json!([]);
|
||||
ui.save(db).await?;
|
||||
Ok(())
|
||||
ui["theme"] = json!("Dark".to_string());
|
||||
ui["widgets"] = json!([]);
|
||||
|
||||
d.as_ui_mut().ser(&ui)
|
||||
})
|
||||
.await
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
let mut ui = crate::db::DatabaseModel::new().ui().get_mut(db).await?;
|
||||
let parsed_url = Some(MAIN_REGISTRY.parse().unwrap());
|
||||
for package_id in crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.keys(db)
|
||||
.await?
|
||||
{
|
||||
if !COMMUNITY_SERVICES.contains(&&*package_id.to_string()) {
|
||||
continue;
|
||||
async fn down(&self, db: PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||
db.mutate(|d| {
|
||||
let mut ui = d.as_ui().de()?;
|
||||
let parsed_url = Some(MAIN_REGISTRY.parse().unwrap());
|
||||
for package_id in d.as_package_data().keys()? {
|
||||
if !COMMUNITY_SERVICES.contains(&&*package_id.to_string()) {
|
||||
continue;
|
||||
}
|
||||
d.as_package_data_mut()
|
||||
.as_idx_mut(&package_id)
|
||||
.or_not_found(&package_id)?
|
||||
.as_installed_mut()
|
||||
.or_not_found(&package_id)?
|
||||
.as_marketplace_url_mut()
|
||||
.ser(&parsed_url)?;
|
||||
}
|
||||
crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&package_id)
|
||||
.expect(db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(db)
|
||||
.await?
|
||||
.marketplace_url()
|
||||
.put(db, &parsed_url)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Value::Object(ref mut obj) = *ui {
|
||||
obj.remove("theme");
|
||||
obj.remove("widgets");
|
||||
}
|
||||
if let imbl_value::Value::Object(ref mut obj) = ui {
|
||||
obj.remove("theme");
|
||||
obj.remove("widgets");
|
||||
}
|
||||
|
||||
ui["marketplace"]["known-hosts"][COMMUNITY_URL].take();
|
||||
ui["marketplace"]["known-hosts"][MAIN_REGISTRY].take();
|
||||
ui.save(db).await?;
|
||||
Ok(())
|
||||
ui["marketplace"]["known-hosts"][COMMUNITY_URL].take();
|
||||
ui["marketplace"]["known-hosts"][MAIN_REGISTRY].take();
|
||||
d.as_ui_mut().ser(&ui)
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use async_trait::async_trait;
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::v0_3_4::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
const V0_3_4_1: emver::Version = emver::Version::new(0, 3, 4, 1);
|
||||
|
||||
@@ -21,10 +22,10 @@ impl VersionT for Version {
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
async fn up(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
async fn down(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use async_trait::async_trait;
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::v0_3_4::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
const V0_3_4_2: emver::Version = emver::Version::new(0, 3, 4, 2);
|
||||
|
||||
@@ -21,10 +22,10 @@ impl VersionT for Version {
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
async fn up(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
async fn down(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use async_trait::async_trait;
|
||||
use emver::VersionRange;
|
||||
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::v0_3_4::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
const V0_3_4_3: emver::Version = emver::Version::new(0, 3, 4, 3);
|
||||
|
||||
@@ -19,18 +20,12 @@ impl VersionT for Version {
|
||||
V0_3_4_3
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.get_mut(db)
|
||||
.await?
|
||||
.save(db)
|
||||
.await?;
|
||||
async fn up(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
async fn down(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use async_trait::async_trait;
|
||||
use emver::VersionRange;
|
||||
use models::ResultExt;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::v0_3_0::V0_3_0_COMPAT;
|
||||
use super::*;
|
||||
use super::v0_3_4::V0_3_0_COMPAT;
|
||||
use super::{v0_3_4_3, VersionT};
|
||||
use crate::prelude::*;
|
||||
|
||||
const V0_3_4_4: emver::Version = emver::Version::new(0, 3, 4, 4);
|
||||
|
||||
@@ -20,22 +22,22 @@ impl VersionT for Version {
|
||||
V0_3_4_4
|
||||
}
|
||||
fn compat(&self) -> &'static VersionRange {
|
||||
&*V0_3_0_COMPAT
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
async fn up<Db: DbHandle>(&self, db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
let mut tor_addr = crate::db::DatabaseModel::new()
|
||||
.server_info()
|
||||
.tor_address()
|
||||
.get_mut(db)
|
||||
.await?;
|
||||
tor_addr
|
||||
.set_scheme("https")
|
||||
.map_err(|_| eyre!("unable to update url scheme to https"))
|
||||
.with_kind(crate::ErrorKind::ParseUrl)?;
|
||||
tor_addr.save(db).await?;
|
||||
async fn up(&self, db: PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||
db.mutate(|v| {
|
||||
let tor_address_lens = v.as_server_info_mut().as_tor_address_mut();
|
||||
let mut tor_addr = tor_address_lens.de()?;
|
||||
tor_addr
|
||||
.set_scheme("https")
|
||||
.map_err(|_| eyre!("unable to update url scheme to https"))
|
||||
.with_kind(crate::ErrorKind::ParseUrl)?;
|
||||
tor_address_lens.ser(&tor_addr)
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn down<Db: DbHandle>(&self, _db: &mut Db, _secrets: &PgPool) -> Result<(), Error> {
|
||||
async fn down(&self, _db: PatchDb, _secrets: &PgPool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
pub use helpers::script_dir;
|
||||
pub use models::VolumeId;
|
||||
use patch_db::{HasModel, Map, MapModel};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::net::interface::{InterfaceId, Interfaces};
|
||||
use crate::net::PACKAGE_CERT_PATH;
|
||||
use crate::prelude::*;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::Version;
|
||||
use crate::{Error, ResultExt};
|
||||
@@ -82,13 +82,6 @@ impl DerefMut for Volumes {
|
||||
impl Map for Volumes {
|
||||
type Key = VolumeId;
|
||||
type Value = Volume;
|
||||
fn get(&self, key: &Self::Key) -> Option<&Self::Value> {
|
||||
self.0.get(key)
|
||||
}
|
||||
}
|
||||
pub type VolumesModel = MapModel<Volumes>;
|
||||
impl HasModel for Volumes {
|
||||
type Model = MapModel<Self>;
|
||||
}
|
||||
|
||||
pub fn data_dir<P: AsRef<Path>>(datadir: P, pkg_id: &PackageId, volume_id: &VolumeId) -> PathBuf {
|
||||
@@ -117,7 +110,7 @@ pub fn cert_dir(pkg_id: &PackageId, interface_id: &InterfaceId) -> PathBuf {
|
||||
Path::new(PACKAGE_CERT_PATH).join(pkg_id).join(interface_id)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum Volume {
|
||||
|
||||
@@ -20,7 +20,13 @@ mount --bind /sys /media/embassy/next/sys
|
||||
mount --bind /proc /media/embassy/next/proc
|
||||
mount --bind /boot /media/embassy/next/boot
|
||||
|
||||
chroot /media/embassy/next $@
|
||||
if [ -z "$*" ]; then
|
||||
chroot /media/embassy/next
|
||||
CHROOT_RES=$?
|
||||
else
|
||||
chroot /media/embassy/next "$SHELL" -c "$*"
|
||||
CHROOT_RES=$?
|
||||
fi
|
||||
|
||||
umount /media/embassy/next/run
|
||||
umount /media/embassy/next/dev
|
||||
@@ -28,10 +34,12 @@ umount /media/embassy/next/sys
|
||||
umount /media/embassy/next/proc
|
||||
umount /media/embassy/next/boot
|
||||
|
||||
echo 'Upgrading...'
|
||||
if [ "$CHROOT_RES" -eq 0 ]; then
|
||||
echo 'Upgrading...'
|
||||
|
||||
touch /media/embassy/config/upgrade
|
||||
touch /media/embassy/config/upgrade
|
||||
|
||||
sync
|
||||
sync
|
||||
|
||||
reboot
|
||||
reboot
|
||||
fi
|
||||
@@ -4,19 +4,21 @@ set -e
|
||||
|
||||
rm -rf frontend/dist/static
|
||||
|
||||
find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 gzip -kf
|
||||
find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 brotli -kf
|
||||
if ! [[ "$ENVIRONMENT" =~ (^|-)dev($|-) ]]; then
|
||||
find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 gzip -kf
|
||||
find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br' | xargs -n 1 -P 0 brotli -kf
|
||||
|
||||
for file in $(find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br'); do
|
||||
raw_size=$(du $file | awk '{print $1 * 512}')
|
||||
gz_size=$(du $file.gz | awk '{print $1 * 512}')
|
||||
br_size=$(du $file.br | awk '{print $1 * 512}')
|
||||
if [ $((gz_size * 100 / raw_size)) -gt 70 ]; then
|
||||
rm $file.gz
|
||||
fi
|
||||
if [ $((br_size * 100 / raw_size)) -gt 70 ]; then
|
||||
rm $file.br
|
||||
fi
|
||||
done
|
||||
for file in $(find frontend/dist/raw -type f -not -name '*.gz' -and -not -name '*.br'); do
|
||||
raw_size=$(du $file | awk '{print $1 * 512}')
|
||||
gz_size=$(du $file.gz | awk '{print $1 * 512}')
|
||||
br_size=$(du $file.br | awk '{print $1 * 512}')
|
||||
if [ $((gz_size * 100 / raw_size)) -gt 70 ]; then
|
||||
rm $file.gz
|
||||
fi
|
||||
if [ $((br_size * 100 / raw_size)) -gt 70 ]; then
|
||||
rm $file.br
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
cp -r frontend/dist/raw frontend/dist/static
|
||||
17478
frontend/package-lock.json
generated
17478
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -73,7 +73,7 @@
|
||||
"node-jose": "^2.2.0",
|
||||
"patch-db-client": "file: ../../../patch-db/client",
|
||||
"pbkdf2": "^3.1.2",
|
||||
"rxjs": "^7.5.6",
|
||||
"rxjs": "^7.8.1",
|
||||
"swiper": "^8.2.4",
|
||||
"ts-matches": "^5.2.1",
|
||||
"tslib": "^2.3.0",
|
||||
@@ -103,7 +103,7 @@
|
||||
"raw-loader": "^4.0.2",
|
||||
"ts-node": "^10.7.0",
|
||||
"tslint": "^6.1.3",
|
||||
"typescript": "^4.6.3",
|
||||
"typescript": "4.8.4",
|
||||
"webpack-bundle-analyzer": "^4.8.0"
|
||||
},
|
||||
"husky": {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="welcome-header">
|
||||
<h1>Welcome to StartOS</h1>
|
||||
</div>
|
||||
<widget-list></widget-list>
|
||||
<!-- <widget-list></widget-list> -->
|
||||
</ng-container>
|
||||
|
||||
<ng-template #list>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { Observable } from 'rxjs'
|
||||
import { Observable, combineLatest } from 'rxjs'
|
||||
import { filter, map, startWith } from 'rxjs/operators'
|
||||
import {
|
||||
DataModel,
|
||||
@@ -7,16 +7,23 @@ import {
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { getPackageInfo, PkgInfo } from '../../../util/get-package-info'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DepErrorService } from 'src/app/services/dep-error.service'
|
||||
|
||||
@Pipe({
|
||||
name: 'packageInfo',
|
||||
})
|
||||
export class PackageInfoPipe implements PipeTransform {
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
constructor(
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly depErrorService: DepErrorService,
|
||||
) {}
|
||||
|
||||
transform(pkg: PackageDataEntry): Observable<PkgInfo> {
|
||||
return this.patch
|
||||
.watch$('package-data', pkg.manifest.id)
|
||||
.pipe(filter(Boolean), startWith(pkg), map(getPackageInfo))
|
||||
return combineLatest([
|
||||
this.patch
|
||||
.watch$('package-data', pkg.manifest.id)
|
||||
.pipe(filter(Boolean), startWith(pkg)),
|
||||
this.depErrorService.depErrors$,
|
||||
]).pipe(map(([pkg, depErrors]) => getPackageInfo(pkg, depErrors)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ import { AppShowAdditionalComponent } from './components/app-show-additional/app
|
||||
import { HealthColorPipe } from './pipes/health-color.pipe'
|
||||
import { ToHealthChecksPipe } from './pipes/to-health-checks.pipe'
|
||||
import { ToButtonsPipe } from './pipes/to-buttons.pipe'
|
||||
import { ToDependenciesPipe } from './pipes/to-dependencies.pipe'
|
||||
import { ToStatusPipe } from './pipes/to-status.pipe'
|
||||
import { ProgressDataPipe } from './pipes/progress-data.pipe'
|
||||
|
||||
const routes: Routes = [
|
||||
@@ -36,8 +34,6 @@ const routes: Routes = [
|
||||
ProgressDataPipe,
|
||||
ToHealthChecksPipe,
|
||||
ToButtonsPipe,
|
||||
ToDependenciesPipe,
|
||||
ToStatusPipe,
|
||||
AppShowHeaderComponent,
|
||||
AppShowProgressComponent,
|
||||
AppShowStatusComponent,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<ng-container *ngIf="pkg$ | async as pkg">
|
||||
<ng-container *ngIf="pkgPlus$ | async as pkgPlus">
|
||||
<!-- header -->
|
||||
<app-show-header [pkg]="pkg"></app-show-header>
|
||||
<app-show-header [pkg]="pkgPlus.pkg"></app-show-header>
|
||||
|
||||
<!-- content -->
|
||||
<ion-content class="ion-padding with-widgets">
|
||||
<ion-content *ngIf="pkgPlus.pkg as pkg" class="ion-padding with-widgets">
|
||||
<!-- ** installing, updating, restoring ** -->
|
||||
<ng-container *ngIf="showProgress(pkg); else installed">
|
||||
<app-show-progress
|
||||
@@ -15,33 +15,27 @@
|
||||
|
||||
<!-- Installed -->
|
||||
<ng-template #installed>
|
||||
<ng-container *ngIf="pkg | toDependencies as dependencies">
|
||||
<ion-item-group *ngIf="pkg | toStatus as status">
|
||||
<!-- ** status ** -->
|
||||
<app-show-status
|
||||
<ion-item-group *ngIf="pkgPlus.status as status">
|
||||
<!-- ** status ** -->
|
||||
<app-show-status [pkg]="pkg" [status]="status"></app-show-status>
|
||||
<!-- ** installed && !backing-up ** -->
|
||||
<ng-container *ngIf="isInstalled(pkg) && !isBackingUp(status)">
|
||||
<!-- ** health checks ** -->
|
||||
<app-show-health-checks
|
||||
*ngIf="isRunning(status)"
|
||||
[pkg]="pkg"
|
||||
[dependencies]="dependencies"
|
||||
[status]="status"
|
||||
></app-show-status>
|
||||
<!-- ** installed && !backing-up ** -->
|
||||
<ng-container *ngIf="isInstalled(pkg) && !isBackingUp(status)">
|
||||
<!-- ** health checks ** -->
|
||||
<app-show-health-checks
|
||||
*ngIf="isRunning(status)"
|
||||
[pkg]="pkg"
|
||||
></app-show-health-checks>
|
||||
<!-- ** dependencies ** -->
|
||||
<app-show-dependencies
|
||||
*ngIf="dependencies.length"
|
||||
[dependencies]="dependencies"
|
||||
></app-show-dependencies>
|
||||
<!-- ** menu ** -->
|
||||
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
|
||||
<!-- ** additional ** -->
|
||||
<app-show-additional [pkg]="pkg"></app-show-additional>
|
||||
</ng-container>
|
||||
</ion-item-group>
|
||||
</ng-container>
|
||||
></app-show-health-checks>
|
||||
<!-- ** dependencies ** -->
|
||||
<app-show-dependencies
|
||||
*ngIf="pkgPlus.dependencies.length"
|
||||
[dependencies]="pkgPlus.dependencies"
|
||||
></app-show-dependencies>
|
||||
<!-- ** menu ** -->
|
||||
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
|
||||
<!-- ** additional ** -->
|
||||
<app-show-additional [pkg]="pkg"></app-show-additional>
|
||||
</ng-container>
|
||||
</ion-item-group>
|
||||
</ng-template>
|
||||
</ion-content>
|
||||
</ng-container>
|
||||
|
||||
@@ -3,16 +3,37 @@ import { NavController } from '@ionic/angular'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
InstalledPackageDataEntry,
|
||||
Manifest,
|
||||
PackageDataEntry,
|
||||
PackageState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import {
|
||||
PackageStatus,
|
||||
PrimaryStatus,
|
||||
renderPkgStatus,
|
||||
} from 'src/app/services/pkg-status-rendering.service'
|
||||
import { tap } from 'rxjs/operators'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { map, tap } from 'rxjs/operators'
|
||||
import { ActivatedRoute, NavigationExtras } from '@angular/router'
|
||||
import { getPkgId } from '@start9labs/shared'
|
||||
import { ModalService } from 'src/app/services/modal.service'
|
||||
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||
import {
|
||||
DepErrorService,
|
||||
DependencyErrorType,
|
||||
PackageDependencyErrors,
|
||||
} from 'src/app/services/dep-error.service'
|
||||
import { combineLatest } from 'rxjs'
|
||||
|
||||
export interface DependencyInfo {
|
||||
id: string
|
||||
title: string
|
||||
icon: string
|
||||
version: string
|
||||
errorText: string
|
||||
actionText: string
|
||||
action: () => any
|
||||
}
|
||||
|
||||
const STATES = [
|
||||
PackageState.Installing,
|
||||
@@ -28,10 +49,21 @@ const STATES = [
|
||||
export class AppShowPage {
|
||||
private readonly pkgId = getPkgId(this.route)
|
||||
|
||||
readonly pkg$ = this.patch.watch$('package-data', this.pkgId).pipe(
|
||||
tap(pkg => {
|
||||
readonly pkgPlus$ = combineLatest([
|
||||
this.patch.watch$('package-data'),
|
||||
this.depErrorService.depErrors$,
|
||||
]).pipe(
|
||||
tap(([pkgs, _]) => {
|
||||
// if package disappears, navigate to list page
|
||||
if (!pkg) this.navCtrl.navigateRoot('/services')
|
||||
if (!pkgs[this.pkgId]) this.navCtrl.navigateRoot('/services')
|
||||
}),
|
||||
map(([pkgs, depErrors]) => {
|
||||
const pkg = pkgs[this.pkgId]
|
||||
return {
|
||||
pkg,
|
||||
dependencies: this.getDepInfo(pkg, depErrors),
|
||||
status: renderPkgStatus(pkg, depErrors),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -39,6 +71,8 @@ export class AppShowPage {
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly modalService: ModalService,
|
||||
private readonly depErrorService: DepErrorService,
|
||||
) {}
|
||||
|
||||
isInstalled({ state }: PackageDataEntry): boolean {
|
||||
@@ -56,4 +90,136 @@ export class AppShowPage {
|
||||
showProgress({ state }: PackageDataEntry): boolean {
|
||||
return STATES.includes(state)
|
||||
}
|
||||
|
||||
private getDepInfo(
|
||||
pkg: PackageDataEntry,
|
||||
depErrors: PackageDependencyErrors,
|
||||
): DependencyInfo[] {
|
||||
const pkgInstalled = pkg.installed
|
||||
|
||||
if (!pkgInstalled) return []
|
||||
|
||||
return Object.keys(pkgInstalled['current-dependencies'])
|
||||
.filter(id => !!pkgInstalled.manifest.dependencies[id])
|
||||
.map(id => this.getDepValues(pkgInstalled, id, depErrors))
|
||||
}
|
||||
|
||||
private getDepValues(
|
||||
pkgInstalled: InstalledPackageDataEntry,
|
||||
depId: string,
|
||||
depErrors: PackageDependencyErrors,
|
||||
): DependencyInfo {
|
||||
const { errorText, fixText, fixAction } = this.getDepErrors(
|
||||
pkgInstalled,
|
||||
depId,
|
||||
depErrors,
|
||||
)
|
||||
|
||||
const depInfo = pkgInstalled['dependency-info'][depId]
|
||||
|
||||
return {
|
||||
id: depId,
|
||||
version: pkgInstalled.manifest.dependencies[depId].version, // do we want this version range?
|
||||
title: depInfo?.manifest?.title || depId,
|
||||
icon: depInfo?.icon || '',
|
||||
errorText: errorText
|
||||
? `${errorText}. ${pkgInstalled.manifest.title} will not work as expected.`
|
||||
: '',
|
||||
actionText: fixText || 'View',
|
||||
action:
|
||||
fixAction || (() => this.navCtrl.navigateForward(`/services/${depId}`)),
|
||||
}
|
||||
}
|
||||
|
||||
private getDepErrors(
|
||||
pkgInstalled: InstalledPackageDataEntry,
|
||||
depId: string,
|
||||
depErrors: PackageDependencyErrors,
|
||||
) {
|
||||
const pkgManifest = pkgInstalled.manifest
|
||||
const depError = depErrors[pkgInstalled.manifest.id][depId]
|
||||
|
||||
let errorText: string | null = null
|
||||
let fixText: string | null = null
|
||||
let fixAction: (() => any) | null = null
|
||||
|
||||
if (depError) {
|
||||
if (depError.type === DependencyErrorType.NotInstalled) {
|
||||
errorText = 'Not installed'
|
||||
fixText = 'Install'
|
||||
fixAction = () => this.fixDep(pkgManifest, 'install', depId)
|
||||
} else if (depError.type === DependencyErrorType.IncorrectVersion) {
|
||||
errorText = 'Incorrect version'
|
||||
fixText = 'Update'
|
||||
fixAction = () => this.fixDep(pkgManifest, 'update', depId)
|
||||
} else if (depError.type === DependencyErrorType.ConfigUnsatisfied) {
|
||||
errorText = 'Config not satisfied'
|
||||
fixText = 'Auto config'
|
||||
fixAction = () => this.fixDep(pkgManifest, 'configure', depId)
|
||||
} else if (depError.type === DependencyErrorType.NotRunning) {
|
||||
errorText = 'Not running'
|
||||
fixText = 'Start'
|
||||
} else if (depError.type === DependencyErrorType.HealthChecksFailed) {
|
||||
errorText = 'Health check failed'
|
||||
} else if (depError.type === DependencyErrorType.Transitive) {
|
||||
errorText = 'Dependency has a dependency issue'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
errorText,
|
||||
fixText,
|
||||
fixAction,
|
||||
}
|
||||
}
|
||||
|
||||
private async fixDep(
|
||||
pkgManifest: Manifest,
|
||||
action: 'install' | 'update' | 'configure',
|
||||
id: string,
|
||||
): Promise<void> {
|
||||
switch (action) {
|
||||
case 'install':
|
||||
case 'update':
|
||||
return this.installDep(pkgManifest, id)
|
||||
case 'configure':
|
||||
return this.configureDep(pkgManifest, id)
|
||||
}
|
||||
}
|
||||
|
||||
private async installDep(
|
||||
pkgManifest: Manifest,
|
||||
depId: string,
|
||||
): Promise<void> {
|
||||
const version = pkgManifest.dependencies[depId].version
|
||||
|
||||
const dependentInfo: DependentInfo = {
|
||||
id: pkgManifest.id,
|
||||
title: pkgManifest.title,
|
||||
version,
|
||||
}
|
||||
const navigationExtras: NavigationExtras = {
|
||||
state: { dependentInfo },
|
||||
}
|
||||
|
||||
await this.navCtrl.navigateForward(
|
||||
`/marketplace/${depId}`,
|
||||
navigationExtras,
|
||||
)
|
||||
}
|
||||
|
||||
private async configureDep(
|
||||
pkgManifest: Manifest,
|
||||
dependencyId: string,
|
||||
): Promise<void> {
|
||||
const dependentInfo: DependentInfo = {
|
||||
id: pkgManifest.id,
|
||||
title: pkgManifest.title,
|
||||
}
|
||||
|
||||
await this.modalService.presentModalConfig({
|
||||
pkgId: dependencyId,
|
||||
dependentInfo,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import { DependencyInfo } from '../../pipes/to-dependencies.pipe'
|
||||
import { DependencyInfo } from '../../app-show.page'
|
||||
|
||||
@Component({
|
||||
selector: 'app-show-dependencies',
|
||||
|
||||
@@ -15,7 +15,6 @@ import { ErrorToastService } from '@start9labs/shared'
|
||||
import { AlertController, LoadingController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ModalService } from 'src/app/services/modal.service'
|
||||
import { DependencyInfo } from '../../pipes/to-dependencies.pipe'
|
||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||
import { ConnectionService } from 'src/app/services/connection.service'
|
||||
|
||||
@@ -32,9 +31,6 @@ export class AppShowStatusComponent {
|
||||
@Input()
|
||||
status!: PackageStatus
|
||||
|
||||
@Input()
|
||||
dependencies: DependencyInfo[] = []
|
||||
|
||||
PR = PrimaryRendering
|
||||
|
||||
readonly connected$ = this.connectionService.connected$
|
||||
@@ -80,7 +76,7 @@ export class AppShowStatusComponent {
|
||||
}
|
||||
|
||||
async tryStart(): Promise<void> {
|
||||
if (this.dependencies.some(d => !!d.errorText)) {
|
||||
if (this.status.dependency === 'warning') {
|
||||
const depErrMsg = `${this.pkg.manifest.title} has unmet dependencies. It will not work as expected.`
|
||||
const proceed = await this.presentAlertStart(depErrMsg)
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { NavigationExtras } from '@angular/router'
|
||||
import { NavController } from '@ionic/angular'
|
||||
import {
|
||||
DependencyError,
|
||||
DependencyErrorType,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||
import { ModalService } from 'src/app/services/modal.service'
|
||||
|
||||
export interface DependencyInfo {
|
||||
id: string
|
||||
title: string
|
||||
icon: string
|
||||
version: string
|
||||
errorText: string
|
||||
actionText: string
|
||||
action: () => any
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'toDependencies',
|
||||
})
|
||||
export class ToDependenciesPipe implements PipeTransform {
|
||||
constructor(
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly modalService: ModalService,
|
||||
) {}
|
||||
|
||||
transform(pkg: PackageDataEntry): DependencyInfo[] {
|
||||
if (!pkg.installed) return []
|
||||
|
||||
return Object.keys(pkg.installed?.['current-dependencies'])
|
||||
.filter(id => !!pkg.manifest.dependencies[id])
|
||||
.map(id =>
|
||||
this.setDepValues(pkg, id, pkg.installed!.status['dependency-errors']),
|
||||
)
|
||||
}
|
||||
|
||||
private setDepValues(
|
||||
pkg: PackageDataEntry,
|
||||
id: string,
|
||||
errors: { [id: string]: DependencyError | null },
|
||||
): DependencyInfo {
|
||||
let errorText = ''
|
||||
let actionText = 'View'
|
||||
let action: () => any = () =>
|
||||
this.navCtrl.navigateForward(`/services/${id}`)
|
||||
|
||||
const error = errors[id]
|
||||
|
||||
if (error) {
|
||||
// health checks failed
|
||||
if (
|
||||
[
|
||||
DependencyErrorType.InterfaceHealthChecksFailed,
|
||||
DependencyErrorType.HealthChecksFailed,
|
||||
].includes(error.type)
|
||||
) {
|
||||
errorText = 'Health check failed'
|
||||
// not installed
|
||||
} else if (error.type === DependencyErrorType.NotInstalled) {
|
||||
errorText = 'Not installed'
|
||||
actionText = 'Install'
|
||||
action = () => this.fixDep(pkg, 'install', id)
|
||||
// incorrect version
|
||||
} else if (error.type === DependencyErrorType.IncorrectVersion) {
|
||||
errorText = 'Incorrect version'
|
||||
actionText = 'Update'
|
||||
action = () => this.fixDep(pkg, 'update', id)
|
||||
// not running
|
||||
} else if (error.type === DependencyErrorType.NotRunning) {
|
||||
errorText = 'Not running'
|
||||
actionText = 'Start'
|
||||
// config unsatisfied
|
||||
} else if (error.type === DependencyErrorType.ConfigUnsatisfied) {
|
||||
errorText = 'Config not satisfied'
|
||||
actionText = 'Auto config'
|
||||
action = () => this.fixDep(pkg, 'configure', id)
|
||||
} else if (error.type === DependencyErrorType.Transitive) {
|
||||
errorText = 'Dependency has a dependency issue'
|
||||
}
|
||||
errorText = `${errorText}. ${pkg.manifest.title} will not work as expected.`
|
||||
}
|
||||
|
||||
const depInfo = pkg.installed?.['dependency-info'][id]
|
||||
|
||||
return {
|
||||
id,
|
||||
version: pkg.manifest.dependencies[id].version,
|
||||
title: depInfo?.manifest?.title || id,
|
||||
icon: depInfo?.icon || '',
|
||||
errorText,
|
||||
actionText,
|
||||
action,
|
||||
}
|
||||
}
|
||||
|
||||
async fixDep(
|
||||
pkg: PackageDataEntry,
|
||||
action: 'install' | 'update' | 'configure',
|
||||
id: string,
|
||||
): Promise<void> {
|
||||
switch (action) {
|
||||
case 'install':
|
||||
case 'update':
|
||||
return this.installDep(pkg, id)
|
||||
case 'configure':
|
||||
return this.configureDep(pkg, id)
|
||||
}
|
||||
}
|
||||
|
||||
private async installDep(
|
||||
pkg: PackageDataEntry,
|
||||
depId: string,
|
||||
): Promise<void> {
|
||||
const version = pkg.manifest.dependencies[depId].version
|
||||
|
||||
const dependentInfo: DependentInfo = {
|
||||
id: pkg.manifest.id,
|
||||
title: pkg.manifest.title,
|
||||
version,
|
||||
}
|
||||
const navigationExtras: NavigationExtras = {
|
||||
state: { dependentInfo },
|
||||
}
|
||||
|
||||
await this.navCtrl.navigateForward(
|
||||
`/marketplace/${depId}`,
|
||||
navigationExtras,
|
||||
)
|
||||
}
|
||||
|
||||
private async configureDep(
|
||||
pkg: PackageDataEntry,
|
||||
dependencyId: string,
|
||||
): Promise<void> {
|
||||
const dependentInfo: DependentInfo = {
|
||||
id: pkg.manifest.id,
|
||||
title: pkg.manifest.title,
|
||||
}
|
||||
|
||||
await this.modalService.presentModalConfig({
|
||||
pkgId: dependencyId,
|
||||
dependentInfo,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import {
|
||||
PackageStatus,
|
||||
renderPkgStatus,
|
||||
} from 'src/app/services/pkg-status-rendering.service'
|
||||
|
||||
@Pipe({
|
||||
name: 'toStatus',
|
||||
})
|
||||
export class ToStatusPipe implements PipeTransform {
|
||||
transform(pkg: PackageDataEntry): PackageStatus {
|
||||
return renderPkgStatus(pkg)
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,10 @@ import {
|
||||
import { ClientStorageService } from 'src/app/services/client-storage.service'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { Breakages } from 'src/app/services/api/api.types'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { getAllPackages } from 'src/app/util/get-package-data'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
import { dryUpdate } from 'src/app/util/dry-update'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show-controls',
|
||||
@@ -57,7 +56,6 @@ export class MarketplaceShowControlsComponent {
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly emver: Emver,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
@@ -142,30 +140,19 @@ export class MarketplaceShowControlsComponent {
|
||||
}
|
||||
|
||||
private async dryInstall(url: string) {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Checking dependent services...',
|
||||
})
|
||||
await loader.present()
|
||||
const breakages = dryUpdate(
|
||||
this.pkg.manifest,
|
||||
await getAllPackages(this.patch),
|
||||
this.emver,
|
||||
)
|
||||
|
||||
const { id, version } = this.pkg.manifest
|
||||
|
||||
try {
|
||||
const breakages = await this.embassyApi.dryUpdatePackage({
|
||||
id,
|
||||
version: `${version}`,
|
||||
})
|
||||
|
||||
if (isEmptyObject(breakages)) {
|
||||
this.install(url, loader)
|
||||
} else {
|
||||
await loader.dismiss()
|
||||
const proceed = await this.presentAlertBreakages(breakages)
|
||||
if (proceed) {
|
||||
this.install(url)
|
||||
}
|
||||
if (isEmptyObject(breakages)) {
|
||||
this.install(url)
|
||||
} else {
|
||||
const proceed = await this.presentAlertBreakages(breakages)
|
||||
if (proceed) {
|
||||
this.install(url)
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,14 +181,11 @@ export class MarketplaceShowControlsComponent {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
private async install(url: string, loader?: HTMLIonLoadingElement) {
|
||||
const message = 'Beginning Install...'
|
||||
if (loader) {
|
||||
loader.message = message
|
||||
} else {
|
||||
loader = await this.loadingCtrl.create({ message })
|
||||
await loader.present()
|
||||
}
|
||||
private async install(url: string) {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Beginning Install...',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
const { id, version } = this.pkg.manifest
|
||||
|
||||
@@ -214,14 +198,10 @@ export class MarketplaceShowControlsComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private async presentAlertBreakages(breakages: Breakages): Promise<boolean> {
|
||||
private async presentAlertBreakages(breakages: string[]): Promise<boolean> {
|
||||
let message: string =
|
||||
'As a result of this update, the following services will no longer work properly and may crash:<ul>'
|
||||
const localPkgs = await getAllPackages(this.patch)
|
||||
const bullets = Object.keys(breakages).map(id => {
|
||||
const title = localPkgs[id].manifest.title
|
||||
return `<li><b>${title}</b></li>`
|
||||
})
|
||||
const bullets = breakages.map(title => `<li><b>${title}</b></li>`)
|
||||
message = `${message}${bullets.join('')}</ul>`
|
||||
|
||||
return new Promise(async resolve => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Component, Inject } from '@angular/core'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
@@ -16,14 +15,10 @@ import {
|
||||
import { Emver, isEmptyObject } from '@start9labs/shared'
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { combineLatest, Observable } from 'rxjs'
|
||||
import {
|
||||
AlertController,
|
||||
LoadingController,
|
||||
NavController,
|
||||
} from '@ionic/angular'
|
||||
import { AlertController, NavController } from '@ionic/angular'
|
||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||
import { getAllPackages } from 'src/app/util/get-package-data'
|
||||
import { Breakages } from 'src/app/services/api/api.types'
|
||||
import { dryUpdate } from 'src/app/util/dry-update'
|
||||
|
||||
interface UpdatesData {
|
||||
hosts: StoreIdentity[]
|
||||
@@ -48,11 +43,10 @@ export class UpdatesPage {
|
||||
constructor(
|
||||
@Inject(AbstractMarketplaceService)
|
||||
readonly marketplaceService: MarketplaceService,
|
||||
private readonly api: ApiService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly emver: Emver,
|
||||
) {}
|
||||
|
||||
viewInMarketplace(event: Event, url: string, id: string) {
|
||||
@@ -77,55 +71,40 @@ export class UpdatesPage {
|
||||
this.marketplaceService.updateQueue[id] = true
|
||||
|
||||
if (hasCurrentDeps(local)) {
|
||||
this.dryUpdate(manifest, url)
|
||||
this.dryInstall(manifest, url)
|
||||
} else {
|
||||
this.update(id, version, url)
|
||||
this.install(id, version, url)
|
||||
}
|
||||
}
|
||||
|
||||
private async dryUpdate(manifest: MarketplaceManifest, url: string) {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Checking dependent services...',
|
||||
})
|
||||
await loader.present()
|
||||
private async dryInstall(manifest: MarketplaceManifest, url: string) {
|
||||
const { id, version, title } = manifest
|
||||
|
||||
const { id, version } = manifest
|
||||
const breakages = dryUpdate(
|
||||
manifest,
|
||||
await getAllPackages(this.patch),
|
||||
this.emver,
|
||||
)
|
||||
|
||||
try {
|
||||
const breakages = await this.api.dryUpdatePackage({
|
||||
id,
|
||||
version: `${version}`,
|
||||
})
|
||||
await loader.dismiss()
|
||||
|
||||
if (isEmptyObject(breakages)) {
|
||||
this.update(id, version, url)
|
||||
if (isEmptyObject(breakages)) {
|
||||
this.install(id, version, url)
|
||||
} else {
|
||||
const proceed = await this.presentAlertBreakages(title, breakages)
|
||||
if (proceed) {
|
||||
this.install(id, version, url)
|
||||
} else {
|
||||
const proceed = await this.presentAlertBreakages(
|
||||
manifest.title,
|
||||
breakages,
|
||||
)
|
||||
if (proceed) {
|
||||
this.update(id, version, url)
|
||||
} else {
|
||||
delete this.marketplaceService.updateQueue[id]
|
||||
}
|
||||
delete this.marketplaceService.updateQueue[id]
|
||||
}
|
||||
} catch (e: any) {
|
||||
delete this.marketplaceService.updateQueue[id]
|
||||
this.marketplaceService.updateErrors[id] = e.message
|
||||
}
|
||||
}
|
||||
|
||||
private async presentAlertBreakages(
|
||||
title: string,
|
||||
breakages: Breakages,
|
||||
breakages: string[],
|
||||
): Promise<boolean> {
|
||||
let message: string = `As a result of updating ${title}, the following services will no longer work properly and may crash:<ul>`
|
||||
const localPkgs = await getAllPackages(this.patch)
|
||||
const bullets = Object.keys(breakages).map(id => {
|
||||
const title = localPkgs[id].manifest.title
|
||||
return `<li><b>${title}</b></li>`
|
||||
const bullets = breakages.map(depTitle => {
|
||||
return `<li><b>${depTitle}</b></li>`
|
||||
})
|
||||
message = `${message}${bullets.join('')}</ul>`
|
||||
|
||||
@@ -156,7 +135,7 @@ export class UpdatesPage {
|
||||
})
|
||||
}
|
||||
|
||||
private async update(id: string, version: string, url: string) {
|
||||
private async install(id: string, version: string, url: string) {
|
||||
try {
|
||||
await this.marketplaceService.installPackage(id, version, url)
|
||||
delete this.marketplaceService.updateQueue[id]
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { PrimaryStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||
import { getPackageInfo, PkgInfo } from '../../../../util/get-package-info'
|
||||
|
||||
@@ -20,11 +23,13 @@ export class HealthComponent {
|
||||
'Transitioning',
|
||||
] as const
|
||||
|
||||
readonly data$ = inject(PatchDB)
|
||||
readonly data$ = inject(PatchDB<DataModel>)
|
||||
.watch$('package-data')
|
||||
.pipe(
|
||||
map(data => {
|
||||
const pkgs = Object.values<PackageDataEntry>(data).map(getPackageInfo)
|
||||
const pkgs = Object.values<PackageDataEntry>(data).map(
|
||||
pkg => getPackageInfo(pkg, {}), // @TODO hack because not currently using widget
|
||||
)
|
||||
const result = this.labels.reduce<Record<string, number>>(
|
||||
(acc, label) => ({
|
||||
...acc,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
DependencyErrorType,
|
||||
DockerIoFormat,
|
||||
Manifest,
|
||||
PackageDataEntry,
|
||||
@@ -1889,7 +1888,7 @@ export module Mock {
|
||||
started: new Date().toISOString(),
|
||||
health: {},
|
||||
},
|
||||
'dependency-errors': {},
|
||||
'dependency-config-errors': {},
|
||||
},
|
||||
'interface-addresses': {
|
||||
ui: {
|
||||
@@ -1935,7 +1934,7 @@ export module Mock {
|
||||
main: {
|
||||
status: PackageMainStatus.Stopped,
|
||||
},
|
||||
'dependency-errors': {},
|
||||
'dependency-config-errors': {},
|
||||
},
|
||||
manifest: MockManifestBitcoinProxy,
|
||||
'interface-addresses': {
|
||||
@@ -1984,10 +1983,8 @@ export module Mock {
|
||||
main: {
|
||||
status: PackageMainStatus.Stopped,
|
||||
},
|
||||
'dependency-errors': {
|
||||
'btc-rpc-proxy': {
|
||||
type: DependencyErrorType.NotInstalled,
|
||||
},
|
||||
'dependency-config-errors': {
|
||||
'btc-rpc-proxy': 'Username not found',
|
||||
},
|
||||
},
|
||||
manifest: MockManifestLnd,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PackagePropertiesVersioned } from 'src/app/util/properties.util'
|
||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||
import {
|
||||
DataModel,
|
||||
DependencyError,
|
||||
HealthCheckResult,
|
||||
Manifest,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { StartOSDiskInfo, LogsRes, ServerLogsReq } from '@start9labs/shared'
|
||||
@@ -201,9 +201,6 @@ export module RR {
|
||||
} // package.install
|
||||
export type InstallPackageRes = null
|
||||
|
||||
export type DryUpdatePackageReq = { id: string; version: string } // package.update.dry
|
||||
export type DryUpdatePackageRes = Breakages
|
||||
|
||||
export type GetPackageConfigReq = { id: string } // package.config.get
|
||||
export type GetPackageConfigRes = { spec: ConfigSpec; config: object }
|
||||
|
||||
@@ -465,3 +462,53 @@ declare global {
|
||||
parse<T>(text: Stringified<T>, reviver?: (key: any, value: any) => any): T
|
||||
}
|
||||
}
|
||||
|
||||
export type Encrypted = {
|
||||
encrypted: string
|
||||
}
|
||||
|
||||
export type DependencyError =
|
||||
| DependencyErrorNotInstalled
|
||||
| DependencyErrorNotRunning
|
||||
| DependencyErrorIncorrectVersion
|
||||
| DependencyErrorConfigUnsatisfied
|
||||
| DependencyErrorHealthChecksFailed
|
||||
| DependencyErrorTransitive
|
||||
|
||||
export enum DependencyErrorType {
|
||||
NotInstalled = 'not-installed',
|
||||
NotRunning = 'not-running',
|
||||
IncorrectVersion = 'incorrect-version',
|
||||
ConfigUnsatisfied = 'config-unsatisfied',
|
||||
HealthChecksFailed = 'health-checks-failed',
|
||||
InterfaceHealthChecksFailed = 'interface-health-checks-failed',
|
||||
Transitive = 'transitive',
|
||||
}
|
||||
|
||||
export interface DependencyErrorNotInstalled {
|
||||
type: DependencyErrorType.NotInstalled
|
||||
}
|
||||
|
||||
export interface DependencyErrorNotRunning {
|
||||
type: DependencyErrorType.NotRunning
|
||||
}
|
||||
|
||||
export interface DependencyErrorIncorrectVersion {
|
||||
type: DependencyErrorType.IncorrectVersion
|
||||
expected: string // version range
|
||||
received: string // version
|
||||
}
|
||||
|
||||
export interface DependencyErrorConfigUnsatisfied {
|
||||
type: DependencyErrorType.ConfigUnsatisfied
|
||||
error: string
|
||||
}
|
||||
|
||||
export interface DependencyErrorHealthChecksFailed {
|
||||
type: DependencyErrorType.HealthChecksFailed
|
||||
check: HealthCheckResult
|
||||
}
|
||||
|
||||
export interface DependencyErrorTransitive {
|
||||
type: DependencyErrorType.Transitive
|
||||
}
|
||||
|
||||
@@ -192,10 +192,6 @@ export abstract class ApiService {
|
||||
params: RR.InstallPackageReq,
|
||||
): Promise<RR.InstallPackageRes>
|
||||
|
||||
abstract dryUpdatePackage(
|
||||
params: RR.DryUpdatePackageReq,
|
||||
): Promise<RR.DryUpdatePackageRes>
|
||||
|
||||
abstract getPackageConfig(
|
||||
params: RR.GetPackageConfigReq,
|
||||
): Promise<RR.GetPackageConfigRes>
|
||||
|
||||
@@ -354,12 +354,6 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'package.install', params })
|
||||
}
|
||||
|
||||
async dryUpdatePackage(
|
||||
params: RR.DryUpdatePackageReq,
|
||||
): Promise<RR.DryUpdatePackageRes> {
|
||||
return this.rpcRequest({ method: 'package.update.dry', params })
|
||||
}
|
||||
|
||||
async getPackageConfig(
|
||||
params: RR.GetPackageConfigReq,
|
||||
): Promise<RR.GetPackageConfigRes> {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user