mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
Merge branch 'integration/new-container-runtime' of github.com:Start9Labs/start-os into integration/new-container-runtime
This commit is contained in:
16
Makefile
16
Makefile
@@ -26,7 +26,7 @@ PATCH_DB_CLIENT_SRC := $(shell git ls-files --recurse-submodules patch-db/client
|
|||||||
GZIP_BIN := $(shell which pigz || which gzip)
|
GZIP_BIN := $(shell which pigz || which gzip)
|
||||||
TAR_BIN := $(shell which gtar || which tar)
|
TAR_BIN := $(shell which gtar || which tar)
|
||||||
COMPILED_TARGETS := $(BINS) system-images/compat/docker-images/$(ARCH).tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar
|
COMPILED_TARGETS := $(BINS) system-images/compat/docker-images/$(ARCH).tar system-images/utils/docker-images/$(ARCH).tar system-images/binfmt/docker-images/$(ARCH).tar
|
||||||
ALL_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; fi) $(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then echo cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console; fi') $(PLATFORM_FILE)
|
ALL_TARGETS := $(STARTD_SRC) $(ENVIRONMENT_FILE) $(GIT_HASH_FILE) $(VERSION_FILE) $(COMPILED_TARGETS) $(shell if [ "$(PLATFORM)" = "raspberrypi" ]; then echo cargo-deps/aarch64-unknown-linux-musl/release/pi-beep; fi) $(shell /bin/bash -c 'if [[ "${ENVIRONMENT}" =~ (^|-)unstable($$|-) ]]; then echo cargo-deps/$(ARCH)-unknown-linux-musl/release/tokio-console; fi') $(PLATFORM_FILE) sdk/lib/test
|
||||||
|
|
||||||
ifeq ($(REMOTE),)
|
ifeq ($(REMOTE),)
|
||||||
mkdir = mkdir -p $1
|
mkdir = mkdir -p $1
|
||||||
@@ -87,7 +87,7 @@ clean:
|
|||||||
format:
|
format:
|
||||||
cd core && cargo +nightly fmt
|
cd core && cargo +nightly fmt
|
||||||
|
|
||||||
test: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
test: $(CORE_SRC) $(ENVIRONMENT_FILE)
|
||||||
cd core && cargo build && cargo test
|
cd core && cargo build && cargo test
|
||||||
|
|
||||||
cli:
|
cli:
|
||||||
@@ -109,7 +109,7 @@ results/$(BASENAME).$(IMAGE_TYPE) results/$(BASENAME).squashfs: $(IMAGE_RECIPE_S
|
|||||||
./image-recipe/run-local-build.sh "results/$(BASENAME).deb"
|
./image-recipe/run-local-build.sh "results/$(BASENAME).deb"
|
||||||
|
|
||||||
# For creating os images. DO NOT USE
|
# For creating os images. DO NOT USE
|
||||||
install: $(ALL_TARGETS)
|
install: $(ALL_TARGETS)
|
||||||
$(call mkdir,$(DESTDIR)/usr/bin)
|
$(call mkdir,$(DESTDIR)/usr/bin)
|
||||||
$(call cp,core/target/$(ARCH)-unknown-linux-musl/release/startbox,$(DESTDIR)/usr/bin/startbox)
|
$(call cp,core/target/$(ARCH)-unknown-linux-musl/release/startbox,$(DESTDIR)/usr/bin/startbox)
|
||||||
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/startd)
|
$(call ln,/usr/bin/startbox,$(DESTDIR)/usr/bin/startd)
|
||||||
@@ -173,11 +173,14 @@ container-runtime/node_modules: container-runtime/package.json container-runtime
|
|||||||
npm --prefix container-runtime ci
|
npm --prefix container-runtime ci
|
||||||
touch container-runtime/node_modules
|
touch container-runtime/node_modules
|
||||||
|
|
||||||
core/startos/bindings: $(CORE_SRC) $(ENVIRONMENT_FILE) $(PLATFORM_FILE)
|
core/startos/bindings: $(shell git ls-files core) $(ENVIRONMENT_FILE) $(PLATFORM_FILE)
|
||||||
(cd core/ && cargo test)
|
(cd core/ && cargo test)
|
||||||
touch core/startos/bindings
|
touch core/startos/bindings
|
||||||
|
|
||||||
sdk/dist: $(shell git ls-files sdk) core/startos/bindings
|
sdk/lib/test: $(shell git ls-files sdk) core/startos/bindings
|
||||||
|
(cd sdk && make test)
|
||||||
|
|
||||||
|
sdk/dist: $(shell git ls-files sdk)
|
||||||
(cd sdk && make bundle)
|
(cd sdk && make bundle)
|
||||||
|
|
||||||
container-runtime/dist: container-runtime/node_modules $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
container-runtime/dist: container-runtime/node_modules $(shell git ls-files container-runtime/src) container-runtime/package.json container-runtime/tsconfig.json
|
||||||
@@ -209,7 +212,8 @@ $(BINS): $(CORE_SRC) $(ENVIRONMENT_FILE)
|
|||||||
cd core && ARCH=$(ARCH) ./build-prod.sh
|
cd core && ARCH=$(ARCH) ./build-prod.sh
|
||||||
touch $(BINS)
|
touch $(BINS)
|
||||||
|
|
||||||
web/node_modules: web/package.json
|
web/node_modules: web/package.json sdk/dist
|
||||||
|
(cd sdk && make bundle)
|
||||||
npm --prefix web ci
|
npm --prefix web ci
|
||||||
|
|
||||||
web/dist/raw/ui: $(WEB_UI_SRC) $(WEB_SHARED_SRC)
|
web/dist/raw/ui: $(WEB_UI_SRC) $(WEB_SHARED_SRC)
|
||||||
|
|||||||
@@ -117,10 +117,7 @@ export class HostSystemStartOs implements Effects {
|
|||||||
T.Effects["createOverlayedImage"]
|
T.Effects["createOverlayedImage"]
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
destroyOverlayedImage(options: {
|
destroyOverlayedImage(options: { guid: string }): Promise<void> {
|
||||||
imageId: string
|
|
||||||
guid: string
|
|
||||||
}): Promise<void> {
|
|
||||||
return this.rpcRound("destroyOverlayedImage", options) as ReturnType<
|
return this.rpcRound("destroyOverlayedImage", options) as ReturnType<
|
||||||
T.Effects["destroyOverlayedImage"]
|
T.Effects["destroyOverlayedImage"]
|
||||||
>
|
>
|
||||||
@@ -196,16 +193,13 @@ export class HostSystemStartOs implements Effects {
|
|||||||
T.Effects["getServicePortForward"]
|
T.Effects["getServicePortForward"]
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
getSslCertificate(
|
getSslCertificate(options: Parameters<T.Effects["getSslCertificate"]>[0]) {
|
||||||
...[packageId, algorithm]: Parameters<T.Effects["getSslCertificate"]>
|
return this.rpcRound("getSslCertificate", options) as ReturnType<
|
||||||
) {
|
T.Effects["getSslCertificate"]
|
||||||
return this.rpcRound("getSslCertificate", {
|
>
|
||||||
packageId,
|
|
||||||
algorithm,
|
|
||||||
}) as ReturnType<T.Effects["getSslCertificate"]>
|
|
||||||
}
|
}
|
||||||
getSslKey(...[packageId, algorithm]: Parameters<T.Effects["getSslKey"]>) {
|
getSslKey(options: Parameters<T.Effects["getSslKey"]>[0]) {
|
||||||
return this.rpcRound("getSslKey", { packageId, algorithm }) as ReturnType<
|
return this.rpcRound("getSslKey", options) as ReturnType<
|
||||||
T.Effects["getSslKey"]
|
T.Effects["getSslKey"]
|
||||||
>
|
>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,11 +33,16 @@ export class DockerProcedureContainer {
|
|||||||
await overlay.mount({ type: "assets", id: mount }, mounts[mount])
|
await overlay.mount({ type: "assets", id: mount }, mounts[mount])
|
||||||
} else if (volumeMount.type === "certificate") {
|
} else if (volumeMount.type === "certificate") {
|
||||||
volumeMount
|
volumeMount
|
||||||
const certChain = await effects.getSslCertificate(
|
const certChain = await effects.getSslCertificate({
|
||||||
null,
|
packageId: null,
|
||||||
volumeMount["interface-id"],
|
hostId: volumeMount["interface-id"],
|
||||||
)
|
algorithm: null,
|
||||||
const key = await effects.getSslKey(null, volumeMount["interface-id"])
|
})
|
||||||
|
const key = await effects.getSslKey({
|
||||||
|
packageId: null,
|
||||||
|
hostId: volumeMount["interface-id"],
|
||||||
|
algorithm: null,
|
||||||
|
})
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
`${path}/${volumeMount["interface-id"]}.cert.pem`,
|
`${path}/${volumeMount["interface-id"]}.cert.pem`,
|
||||||
certChain.join("\n"),
|
certChain.join("\n"),
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ export class MainLoop {
|
|||||||
|
|
||||||
if ("result" in result) {
|
if ("result" in result) {
|
||||||
await effects.setHealth({
|
await effects.setHealth({
|
||||||
|
message: null,
|
||||||
name: healthId,
|
name: healthId,
|
||||||
status: "passing",
|
status: "passing",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
|
||||||
use crate::Id;
|
use crate::{Id, InvalidId};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, ts_rs::TS)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, ts_rs::TS)]
|
||||||
pub struct HealthCheckId(Id);
|
pub struct HealthCheckId(Id);
|
||||||
@@ -11,6 +12,12 @@ impl std::fmt::Display for HealthCheckId {
|
|||||||
write!(f, "{}", &self.0)
|
write!(f, "{}", &self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl FromStr for HealthCheckId {
|
||||||
|
type Err = InvalidId;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Id::from_str(s).map(HealthCheckId)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl AsRef<str> for HealthCheckId {
|
impl AsRef<str> for HealthCheckId {
|
||||||
fn as_ref(&self) -> &str {
|
fn as_ref(&self) -> &str {
|
||||||
self.0.as_ref()
|
self.0.as_ref()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
@@ -59,6 +60,12 @@ impl TryFrom<&str> for Id {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl FromStr for Id {
|
||||||
|
type Err = InvalidId;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Self::try_from(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl From<Id> for InternedString {
|
impl From<Id> for InternedString {
|
||||||
fn from(value: Id) -> Self {
|
fn from(value: Id) -> Self {
|
||||||
value.0
|
value.0
|
||||||
|
|||||||
4
core/startos/bindings/AddressInfo.ts
Normal file
4
core/startos/bindings/AddressInfo.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { BindOptions } from "./BindOptions";
|
||||||
|
|
||||||
|
export interface AddressInfo { username: string | null, hostId: string, bindOptions: BindOptions, suffix: string, }
|
||||||
3
core/startos/bindings/AllowedStatuses.ts
Normal file
3
core/startos/bindings/AllowedStatuses.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type AllowedStatuses = "only-running" | "only-stopped" | "any" | "disabled";
|
||||||
5
core/startos/bindings/BindOptions.ts
Normal file
5
core/startos/bindings/BindOptions.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { AddSslOptions } from "./AddSslOptions";
|
||||||
|
import type { BindOptionsSecure } from "./BindOptionsSecure";
|
||||||
|
|
||||||
|
export interface BindOptions { scheme: string | null, preferredExternalPort: number, addSsl: AddSslOptions | null, secure: BindOptionsSecure | null, }
|
||||||
3
core/startos/bindings/BindOptionsSecure.ts
Normal file
3
core/startos/bindings/BindOptionsSecure.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface BindOptionsSecure { ssl: boolean, }
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { AddSslOptions } from "./AddSslOptions";
|
import type { AddSslOptions } from "./AddSslOptions";
|
||||||
import type { BindKind } from "./BindKind";
|
import type { BindKind } from "./BindKind";
|
||||||
|
import type { BindOptionsSecure } from "./BindOptionsSecure";
|
||||||
|
|
||||||
export interface BindParams { kind: BindKind, id: string, internalPort: number, scheme: string, preferredExternalPort: number, addSsl: AddSslOptions | null, secure: boolean, ssl: boolean, }
|
export interface BindParams { kind: BindKind, id: string, internalPort: number, scheme: string, preferredExternalPort: number, addSsl: AddSslOptions | null, secure: BindOptionsSecure | null, }
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { ImageId } from "./ImageId";
|
|
||||||
|
|
||||||
export interface CreateOverlayedImageParams { imageId: ImageId, }
|
export interface CreateOverlayedImageParams { imageId: string, }
|
||||||
3
core/startos/bindings/DependencyKind.ts
Normal file
3
core/startos/bindings/DependencyKind.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type DependencyKind = "exists" | "running";
|
||||||
4
core/startos/bindings/DependencyRequirement.ts
Normal file
4
core/startos/bindings/DependencyRequirement.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { DependencyKind } from "./DependencyKind";
|
||||||
|
|
||||||
|
export interface DependencyRequirement { id: string, kind: DependencyKind, healthChecks: string[], }
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { ImageId } from "./ImageId";
|
|
||||||
|
|
||||||
export interface DestroyOverlayedImageParams { imageId: ImageId, guid: string, }
|
export interface DestroyOverlayedImageParams { guid: string, }
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { ActionId } from "./ActionId";
|
|
||||||
import type { PackageId } from "./PackageId";
|
|
||||||
|
|
||||||
export interface ExecuteAction { serviceId: PackageId | null, actionId: ActionId, input: any, }
|
export interface ExecuteAction { serviceId: string | null, actionId: string, input: any, }
|
||||||
4
core/startos/bindings/ExportActionParams.ts
Normal file
4
core/startos/bindings/ExportActionParams.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { AllowedStatuses } from "./AllowedStatuses";
|
||||||
|
|
||||||
|
export interface ExportActionParams { name: string, description: string, id: string, input: {[key: string]: any}, allowedStatuses: AllowedStatuses, group: string | null, }
|
||||||
5
core/startos/bindings/ExportServiceInterfaceParams.ts
Normal file
5
core/startos/bindings/ExportServiceInterfaceParams.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { AddressInfo } from "./AddressInfo";
|
||||||
|
import type { ServiceInterfaceType } from "./ServiceInterfaceType";
|
||||||
|
|
||||||
|
export interface ExportServiceInterfaceParams { id: string, name: string, description: string, hasPrimary: boolean, disabled: boolean, masked: boolean, addressInfo: AddressInfo, type: ServiceInterfaceType, }
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { ExposedUI } from "./ExposedUI";
|
|
||||||
|
|
||||||
export interface ExposeUiParams { paths: Array<ExposedUI>, }
|
export type ExposeUiParams = { "type": "object", value: {[key: string]: ExposeUiParams}, } | { "type": "string", path: string, description: string | null, masked: boolean, copyable: boolean | null, qr: boolean | null, };
|
||||||
3
core/startos/bindings/ExposedUI.ts
Normal file
3
core/startos/bindings/ExposedUI.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface ExposedUI { path: string, title: string, description: string | null, masked: boolean | null, copyable: boolean | null, qr: boolean | null, }
|
||||||
4
core/startos/bindings/GetPrimaryUrlParams.ts
Normal file
4
core/startos/bindings/GetPrimaryUrlParams.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { Callback } from "./Callback";
|
||||||
|
|
||||||
|
export interface GetPrimaryUrlParams { packageId: string | null, serviceInterfaceId: string, callback: Callback, }
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { Callback } from "./Callback";
|
import type { Callback } from "./Callback";
|
||||||
import type { PackageId } from "./PackageId";
|
|
||||||
|
|
||||||
export interface GetServiceInterfaceParams { packageId: PackageId | null, serviceInterfaceId: string, callback: Callback, }
|
export interface GetServiceInterfaceParams { packageId: string | null, serviceInterfaceId: string, callback: Callback, }
|
||||||
3
core/startos/bindings/GetServicePortForwardParams.ts
Normal file
3
core/startos/bindings/GetServicePortForwardParams.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface GetServicePortForwardParams { packageId: string | null, internalPort: number, }
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { PackageId } from "./PackageId";
|
|
||||||
|
|
||||||
export interface GetStoreParams { packageId: PackageId | null, path: string, }
|
export interface GetStoreParams { packageId: string | null, path: string, }
|
||||||
4
core/startos/bindings/GetSystemSmtpParams.ts
Normal file
4
core/startos/bindings/GetSystemSmtpParams.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { Callback } from "./Callback";
|
||||||
|
|
||||||
|
export interface GetSystemSmtpParams { callback: Callback, }
|
||||||
3
core/startos/bindings/HealthCheckString.ts
Normal file
3
core/startos/bindings/HealthCheckString.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type HealthCheckString = "passing" | "disabled" | "starting" | "warning" | "failure";
|
||||||
4
core/startos/bindings/ListServiceInterfacesParams.ts
Normal file
4
core/startos/bindings/ListServiceInterfacesParams.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { Callback } from "./Callback";
|
||||||
|
|
||||||
|
export interface ListServiceInterfacesParams { packageId: string | null, callback: Callback, }
|
||||||
4
core/startos/bindings/MountParams.ts
Normal file
4
core/startos/bindings/MountParams.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { MountTarget } from "./MountTarget";
|
||||||
|
|
||||||
|
export interface MountParams { location: string, target: MountTarget, }
|
||||||
3
core/startos/bindings/MountTarget.ts
Normal file
3
core/startos/bindings/MountTarget.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface MountTarget { packageId: string, volumeId: string, path: string, readonly: boolean, }
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { PackageId } from "./PackageId";
|
|
||||||
|
|
||||||
export interface ParamsMaybePackageId { packageId: PackageId | null, }
|
export interface ParamsMaybePackageId { packageId: string | null, }
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { PackageId } from "./PackageId";
|
|
||||||
|
|
||||||
export interface ParamsPackageId { packageId: PackageId, }
|
export interface ParamsPackageId { packageId: string, }
|
||||||
3
core/startos/bindings/RemoveActionParams.ts
Normal file
3
core/startos/bindings/RemoveActionParams.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface RemoveActionParams { id: string, }
|
||||||
3
core/startos/bindings/RemoveAddressParams.ts
Normal file
3
core/startos/bindings/RemoveAddressParams.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface RemoveAddressParams { id: string, }
|
||||||
3
core/startos/bindings/ReverseProxyBind.ts
Normal file
3
core/startos/bindings/ReverseProxyBind.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface ReverseProxyBind { ip: string | null, port: number, ssl: boolean, }
|
||||||
3
core/startos/bindings/ReverseProxyDestination.ts
Normal file
3
core/startos/bindings/ReverseProxyDestination.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface ReverseProxyDestination { ip: string | null, port: number, ssl: boolean, }
|
||||||
3
core/startos/bindings/ReverseProxyHttp.ts
Normal file
3
core/startos/bindings/ReverseProxyHttp.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export interface ReverseProxyHttp { headers: null | {[key: string]: string}, }
|
||||||
6
core/startos/bindings/ReverseProxyParams.ts
Normal file
6
core/startos/bindings/ReverseProxyParams.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { ReverseProxyBind } from "./ReverseProxyBind";
|
||||||
|
import type { ReverseProxyDestination } from "./ReverseProxyDestination";
|
||||||
|
import type { ReverseProxyHttp } from "./ReverseProxyHttp";
|
||||||
|
|
||||||
|
export interface ReverseProxyParams { bind: ReverseProxyBind, dst: ReverseProxyDestination, http: ReverseProxyHttp, }
|
||||||
3
core/startos/bindings/ServiceInterfaceType.ts
Normal file
3
core/startos/bindings/ServiceInterfaceType.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type ServiceInterfaceType = "ui" | "p2p" | "api";
|
||||||
4
core/startos/bindings/SetDependenciesParams.ts
Normal file
4
core/startos/bindings/SetDependenciesParams.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
import type { DependencyRequirement } from "./DependencyRequirement";
|
||||||
|
|
||||||
|
export interface SetDependenciesParams { dependencies: Array<DependencyRequirement>, }
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
import type { HealthCheckId } from "./HealthCheckId";
|
|
||||||
import type { HealthCheckString } from "./HealthCheckString";
|
import type { HealthCheckString } from "./HealthCheckString";
|
||||||
|
|
||||||
export interface SetHealth { name: HealthCheckId, status: HealthCheckString, message: string | null, }
|
export interface SetHealth { name: string, status: HealthCheckString, message: string | null, }
|
||||||
@@ -18,7 +18,8 @@ use crate::auth::check_password_against_db;
|
|||||||
use crate::backup::os::OsBackup;
|
use crate::backup::os::OsBackup;
|
||||||
use crate::backup::{BackupReport, ServerBackupReport};
|
use crate::backup::{BackupReport, ServerBackupReport};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::db::model::{BackupProgress, DatabaseModel};
|
use crate::db::model::public::BackupProgress;
|
||||||
|
use crate::db::model::DatabaseModel;
|
||||||
use crate::disk::mount::backup::BackupMountGuard;
|
use crate::disk::mount::backup::BackupMountGuard;
|
||||||
use crate::disk::mount::filesystem::ReadWrite;
|
use crate::disk::mount::filesystem::ReadWrite;
|
||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
@@ -174,7 +175,7 @@ pub async fn backup_all(
|
|||||||
.as_package_data()
|
.as_package_data()
|
||||||
.as_entries()?
|
.as_entries()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|(_, m)| m.expect_as_installed().is_ok())
|
.filter(|(_, m)| m.as_state_info().expect_installed().is_ok())
|
||||||
.map(|(id, _)| id)
|
.map(|(id, _)| id)
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use super::setup::CURRENT_SECRET;
|
|||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::core::rpc_continuations::{RequestGuid, RestHandler, RpcContinuation, WebSocketHandler};
|
use crate::core::rpc_continuations::{RequestGuid, RestHandler, RpcContinuation, WebSocketHandler};
|
||||||
use crate::db::model::CurrentDependents;
|
use crate::db::model::package::CurrentDependents;
|
||||||
use crate::db::prelude::PatchDbExt;
|
use crate::db::prelude::PatchDbExt;
|
||||||
use crate::dependencies::compute_dependency_config_errs;
|
use crate::dependencies::compute_dependency_config_errs;
|
||||||
use crate::disk::OsPartitionInfo;
|
use crate::disk::OsPartitionInfo;
|
||||||
@@ -219,12 +219,7 @@ impl RpcContext {
|
|||||||
for (package_id, package) in
|
for (package_id, package) in
|
||||||
f.as_public_mut().as_package_data_mut().as_entries_mut()?
|
f.as_public_mut().as_package_data_mut().as_entries_mut()?
|
||||||
{
|
{
|
||||||
for (k, v) in package
|
for (k, v) in package.clone().into_current_dependencies().into_entries()? {
|
||||||
.as_installed_mut()
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|i| i.clone().into_current_dependencies().into_entries())
|
|
||||||
.flatten()
|
|
||||||
{
|
|
||||||
let mut entry: BTreeMap<_, _> =
|
let mut entry: BTreeMap<_, _> =
|
||||||
current_dependents.remove(&k).unwrap_or_default();
|
current_dependents.remove(&k).unwrap_or_default();
|
||||||
entry.insert(package_id.clone(), v.de()?);
|
entry.insert(package_id.clone(), v.de()?);
|
||||||
@@ -236,16 +231,7 @@ impl RpcContext {
|
|||||||
.as_public_mut()
|
.as_public_mut()
|
||||||
.as_package_data_mut()
|
.as_package_data_mut()
|
||||||
.as_idx_mut(&package_id)
|
.as_idx_mut(&package_id)
|
||||||
.and_then(|pde| pde.expect_as_installed_mut().ok())
|
.map(|i| i.as_current_dependents_mut())
|
||||||
.map(|i| i.as_installed_mut().as_current_dependents_mut())
|
|
||||||
{
|
|
||||||
deps.ser(&CurrentDependents(current_dependents))?;
|
|
||||||
} else if let Some(deps) = f
|
|
||||||
.as_public_mut()
|
|
||||||
.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))?;
|
deps.ser(&CurrentDependents(current_dependents))?;
|
||||||
}
|
}
|
||||||
@@ -261,23 +247,18 @@ impl RpcContext {
|
|||||||
let peek = self.db.peek().await;
|
let peek = self.db.peek().await;
|
||||||
for (package_id, package) in peek.as_public().as_package_data().as_entries()?.into_iter() {
|
for (package_id, package) in peek.as_public().as_package_data().as_entries()?.into_iter() {
|
||||||
let package = package.clone();
|
let package = package.clone();
|
||||||
if let Some(current_dependencies) = package
|
let current_dependencies = package.as_current_dependencies().de()?;
|
||||||
.as_installed()
|
all_dependency_config_errs.insert(
|
||||||
.and_then(|x| x.as_current_dependencies().de().ok())
|
package_id.clone(),
|
||||||
{
|
compute_dependency_config_errs(
|
||||||
let manifest = package.as_manifest().de()?;
|
self,
|
||||||
all_dependency_config_errs.insert(
|
&peek,
|
||||||
package_id.clone(),
|
&package_id,
|
||||||
compute_dependency_config_errs(
|
¤t_dependencies,
|
||||||
self,
|
&Default::default(),
|
||||||
&peek,
|
)
|
||||||
&manifest,
|
.await?,
|
||||||
¤t_dependencies,
|
);
|
||||||
&Default::default(),
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.db
|
self.db
|
||||||
.mutate(|v| {
|
.mutate(|v| {
|
||||||
@@ -286,7 +267,6 @@ impl RpcContext {
|
|||||||
.as_public_mut()
|
.as_public_mut()
|
||||||
.as_package_data_mut()
|
.as_package_data_mut()
|
||||||
.as_idx_mut(&package_id)
|
.as_idx_mut(&package_id)
|
||||||
.and_then(|pde| pde.as_installed_mut())
|
|
||||||
.map(|i| i.as_status_mut().as_dependency_config_errors_mut())
|
.map(|i| i.as_status_mut().as_dependency_config_errors_mut())
|
||||||
{
|
{
|
||||||
config_errors.ser(&errs)?;
|
config_errors.ser(&errs)?;
|
||||||
|
|||||||
@@ -1,626 +0,0 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use emver::VersionRange;
|
|
||||||
use imbl_value::InternedString;
|
|
||||||
use ipnet::{Ipv4Net, Ipv6Net};
|
|
||||||
use isocountry::CountryCode;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use models::{DataUrl, HealthCheckId, HostId, PackageId};
|
|
||||||
use openssl::hash::MessageDigest;
|
|
||||||
use patch_db::json_ptr::JsonPointer;
|
|
||||||
use patch_db::{HasModel, Value};
|
|
||||||
use reqwest::Url;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use torut::onion::OnionAddressV3;
|
|
||||||
|
|
||||||
use crate::account::AccountInfo;
|
|
||||||
use crate::auth::Sessions;
|
|
||||||
use crate::backup::target::cifs::CifsTargets;
|
|
||||||
use crate::net::forward::AvailablePorts;
|
|
||||||
use crate::net::host::HostInfo;
|
|
||||||
use crate::net::keys::KeyStore;
|
|
||||||
use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr};
|
|
||||||
use crate::notifications::Notifications;
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::progress::FullProgress;
|
|
||||||
use crate::s9pk::manifest::Manifest;
|
|
||||||
use crate::ssh::SshKeys;
|
|
||||||
use crate::status::Status;
|
|
||||||
use crate::util::cpupower::Governor;
|
|
||||||
use crate::util::serde::Pem;
|
|
||||||
use crate::util::Version;
|
|
||||||
use crate::version::{Current, VersionT};
|
|
||||||
use crate::{ARCH, PLATFORM};
|
|
||||||
|
|
||||||
fn get_arch() -> InternedString {
|
|
||||||
(*ARCH).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_platform() -> InternedString {
|
|
||||||
(&*PLATFORM).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
#[model = "Model<Self>"]
|
|
||||||
pub struct Database {
|
|
||||||
pub public: Public,
|
|
||||||
pub private: Private,
|
|
||||||
}
|
|
||||||
impl Database {
|
|
||||||
pub fn init(account: &AccountInfo) -> Result<Self, Error> {
|
|
||||||
let lan_address = account.hostname.lan_address().parse().unwrap();
|
|
||||||
Ok(Database {
|
|
||||||
public: Public {
|
|
||||||
server_info: ServerInfo {
|
|
||||||
arch: get_arch(),
|
|
||||||
platform: get_platform(),
|
|
||||||
id: account.server_id.clone(),
|
|
||||||
version: Current::new().semver().into(),
|
|
||||||
hostname: account.hostname.no_dot_host_name(),
|
|
||||||
last_backup: None,
|
|
||||||
last_wifi_region: None,
|
|
||||||
eos_version_compat: Current::new().compat().clone(),
|
|
||||||
lan_address,
|
|
||||||
onion_address: account.tor_key.public().get_onion_address(),
|
|
||||||
tor_address: format!(
|
|
||||||
"https://{}",
|
|
||||||
account.tor_key.public().get_onion_address()
|
|
||||||
)
|
|
||||||
.parse()
|
|
||||||
.unwrap(),
|
|
||||||
ip_info: BTreeMap::new(),
|
|
||||||
status_info: ServerStatus {
|
|
||||||
backup_progress: None,
|
|
||||||
updated: false,
|
|
||||||
update_progress: None,
|
|
||||||
shutting_down: false,
|
|
||||||
restarting: false,
|
|
||||||
},
|
|
||||||
wifi: WifiInfo {
|
|
||||||
ssids: Vec::new(),
|
|
||||||
connected: None,
|
|
||||||
selected: None,
|
|
||||||
},
|
|
||||||
unread_notification_count: 0,
|
|
||||||
connection_addresses: ConnectionAddresses {
|
|
||||||
tor: Vec::new(),
|
|
||||||
clearnet: Vec::new(),
|
|
||||||
},
|
|
||||||
password_hash: account.password.clone(),
|
|
||||||
pubkey: ssh_key::PublicKey::from(&account.ssh_key)
|
|
||||||
.to_openssh()
|
|
||||||
.unwrap(),
|
|
||||||
ca_fingerprint: account
|
|
||||||
.root_ca_cert
|
|
||||||
.digest(MessageDigest::sha256())
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.map(|x| format!("{x:X}"))
|
|
||||||
.join(":"),
|
|
||||||
ntp_synced: false,
|
|
||||||
zram: true,
|
|
||||||
governor: None,
|
|
||||||
},
|
|
||||||
package_data: AllPackageData::default(),
|
|
||||||
ui: serde_json::from_str(include_str!(concat!(
|
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
|
||||||
"/../../web/patchdb-ui-seed.json"
|
|
||||||
)))
|
|
||||||
.unwrap(),
|
|
||||||
},
|
|
||||||
private: Private {
|
|
||||||
key_store: KeyStore::new(account)?,
|
|
||||||
password: account.password.clone(),
|
|
||||||
ssh_privkey: Pem(account.ssh_key.clone()),
|
|
||||||
ssh_pubkeys: SshKeys::new(),
|
|
||||||
available_ports: AvailablePorts::new(),
|
|
||||||
sessions: Sessions::new(),
|
|
||||||
notifications: Notifications::new(),
|
|
||||||
cifs: CifsTargets::new(),
|
|
||||||
package_stores: BTreeMap::new(),
|
|
||||||
}, // TODO
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type DatabaseModel = Model<Database>;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
#[model = "Model<Self>"]
|
|
||||||
// #[macro_debug]
|
|
||||||
pub struct Public {
|
|
||||||
pub server_info: ServerInfo,
|
|
||||||
pub package_data: AllPackageData,
|
|
||||||
pub ui: Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
#[model = "Model<Self>"]
|
|
||||||
pub struct Private {
|
|
||||||
pub key_store: KeyStore,
|
|
||||||
pub password: String, // argon2 hash
|
|
||||||
pub ssh_privkey: Pem<ssh_key::PrivateKey>,
|
|
||||||
pub ssh_pubkeys: SshKeys,
|
|
||||||
pub available_ports: AvailablePorts,
|
|
||||||
pub sessions: Sessions,
|
|
||||||
pub notifications: Notifications,
|
|
||||||
pub cifs: CifsTargets,
|
|
||||||
#[serde(default)]
|
|
||||||
pub package_stores: BTreeMap<PackageId, Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
#[model = "Model<Self>"]
|
|
||||||
pub struct ServerInfo {
|
|
||||||
#[serde(default = "get_arch")]
|
|
||||||
pub arch: InternedString,
|
|
||||||
#[serde(default = "get_platform")]
|
|
||||||
pub platform: InternedString,
|
|
||||||
pub id: String,
|
|
||||||
pub hostname: String,
|
|
||||||
pub version: Version,
|
|
||||||
pub last_backup: Option<DateTime<Utc>>,
|
|
||||||
/// Used in the wifi to determine the region to set the system to
|
|
||||||
pub last_wifi_region: Option<CountryCode>,
|
|
||||||
pub eos_version_compat: VersionRange,
|
|
||||||
pub lan_address: Url,
|
|
||||||
pub onion_address: OnionAddressV3,
|
|
||||||
/// for backwards compatibility
|
|
||||||
pub tor_address: Url,
|
|
||||||
pub ip_info: BTreeMap<String, IpInfo>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub status_info: ServerStatus,
|
|
||||||
pub wifi: WifiInfo,
|
|
||||||
pub unread_notification_count: u64,
|
|
||||||
pub connection_addresses: ConnectionAddresses,
|
|
||||||
pub password_hash: String,
|
|
||||||
pub pubkey: String,
|
|
||||||
pub ca_fingerprint: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub ntp_synced: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub zram: bool,
|
|
||||||
pub governor: Option<Governor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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>,
|
|
||||||
pub ipv6_range: Option<Ipv6Net>,
|
|
||||||
pub ipv6: Option<Ipv6Addr>,
|
|
||||||
}
|
|
||||||
impl IpInfo {
|
|
||||||
pub async fn for_interface(iface: &str) -> Result<Self, Error> {
|
|
||||||
let (ipv4, ipv4_range) = get_iface_ipv4_addr(iface).await?.unzip();
|
|
||||||
let (ipv6, ipv6_range) = get_iface_ipv6_addr(iface).await?.unzip();
|
|
||||||
Ok(Self {
|
|
||||||
ipv4_range,
|
|
||||||
ipv4,
|
|
||||||
ipv6_range,
|
|
||||||
ipv6,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {
|
|
||||||
pub backup_progress: Option<BTreeMap<PackageId, BackupProgress>>,
|
|
||||||
pub updated: bool,
|
|
||||||
pub update_progress: Option<UpdateProgress>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub shutting_down: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub restarting: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
#[model = "Model<Self>"]
|
|
||||||
pub struct UpdateProgress {
|
|
||||||
pub size: Option<u64>,
|
|
||||||
pub downloaded: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
#[model = "Model<Self>"]
|
|
||||||
pub struct WifiInfo {
|
|
||||||
pub ssids: Vec<String>,
|
|
||||||
pub selected: Option<String>,
|
|
||||||
pub connected: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub struct ServerSpecs {
|
|
||||||
pub cpu: String,
|
|
||||||
pub disk: String,
|
|
||||||
pub memory: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub struct ConnectionAddresses {
|
|
||||||
pub tor: Vec<String>,
|
|
||||||
pub clearnet: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
|
||||||
pub struct AllPackageData(pub BTreeMap<PackageId, PackageDataEntry>);
|
|
||||||
impl Map for AllPackageData {
|
|
||||||
type Key = PackageId;
|
|
||||||
type Value = PackageDataEntry;
|
|
||||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
|
||||||
Ok(key)
|
|
||||||
}
|
|
||||||
fn key_string(key: &Self::Key) -> Result<InternedString, Error> {
|
|
||||||
Ok(key.clone().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
#[model = "Model<Self>"]
|
|
||||||
pub struct StaticFiles {
|
|
||||||
license: String,
|
|
||||||
instructions: String,
|
|
||||||
icon: DataUrl<'static>,
|
|
||||||
}
|
|
||||||
impl StaticFiles {
|
|
||||||
pub fn local(id: &PackageId, version: &Version, icon: DataUrl<'static>) -> Self {
|
|
||||||
StaticFiles {
|
|
||||||
license: format!("/public/package-data/{}/{}/LICENSE.md", id, version),
|
|
||||||
instructions: format!("/public/package-data/{}/{}/INSTRUCTIONS.md", id, version),
|
|
||||||
icon,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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: FullProgress,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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: FullProgress,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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: FullProgress,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {
|
|
||||||
Installing(PackageDataEntryInstalling),
|
|
||||||
Updating(PackageDataEntryUpdating),
|
|
||||||
Restoring(PackageDataEntryRestoring),
|
|
||||||
Removing(PackageDataEntryRemoving),
|
|
||||||
Installed(PackageDataEntryInstalled),
|
|
||||||
}
|
|
||||||
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 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 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 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 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,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 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 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 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<FullProgress>> {
|
|
||||||
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<FullProgress>> {
|
|
||||||
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")]
|
|
||||||
#[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::VerifyingKey,
|
|
||||||
pub manifest: Manifest,
|
|
||||||
pub last_backup: Option<DateTime<Utc>>,
|
|
||||||
pub dependency_info: BTreeMap<PackageId, StaticDependencyInfo>,
|
|
||||||
pub current_dependents: CurrentDependents,
|
|
||||||
pub current_dependencies: CurrentDependencies,
|
|
||||||
pub interface_addresses: InterfaceAddressMap,
|
|
||||||
pub hosts: HostInfo,
|
|
||||||
pub store_exposed_ui: Vec<ExposedUI>,
|
|
||||||
pub store_exposed_dependents: Vec<JsonPointer>,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
|
||||||
#[model = "Model<Self>"]
|
|
||||||
pub struct ExposedDependent {
|
|
||||||
path: String,
|
|
||||||
title: String,
|
|
||||||
description: Option<String>,
|
|
||||||
masked: Option<bool>,
|
|
||||||
copyable: Option<bool>,
|
|
||||||
qr: Option<bool>,
|
|
||||||
}
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, ts_rs::TS)]
|
|
||||||
#[model = "Model<Self>"]
|
|
||||||
pub struct ExposedUI {
|
|
||||||
#[ts(type = "string")]
|
|
||||||
pub path: JsonPointer,
|
|
||||||
pub title: String,
|
|
||||||
pub description: Option<String>,
|
|
||||||
pub masked: Option<bool>,
|
|
||||||
pub copyable: Option<bool>,
|
|
||||||
pub qr: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
|
||||||
pub struct CurrentDependents(pub BTreeMap<PackageId, CurrentDependencyInfo>);
|
|
||||||
impl CurrentDependents {
|
|
||||||
pub fn map(
|
|
||||||
mut self,
|
|
||||||
transform: impl Fn(
|
|
||||||
BTreeMap<PackageId, CurrentDependencyInfo>,
|
|
||||||
) -> BTreeMap<PackageId, CurrentDependencyInfo>,
|
|
||||||
) -> Self {
|
|
||||||
self.0 = transform(self.0);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Map for CurrentDependents {
|
|
||||||
type Key = PackageId;
|
|
||||||
type Value = CurrentDependencyInfo;
|
|
||||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
|
||||||
Ok(key)
|
|
||||||
}
|
|
||||||
fn key_string(key: &Self::Key) -> Result<InternedString, Error> {
|
|
||||||
Ok(key.clone().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
|
||||||
pub struct CurrentDependencies(pub BTreeMap<PackageId, CurrentDependencyInfo>);
|
|
||||||
impl CurrentDependencies {
|
|
||||||
pub fn map(
|
|
||||||
mut self,
|
|
||||||
transform: impl Fn(
|
|
||||||
BTreeMap<PackageId, CurrentDependencyInfo>,
|
|
||||||
) -> BTreeMap<PackageId, CurrentDependencyInfo>,
|
|
||||||
) -> Self {
|
|
||||||
self.0 = transform(self.0);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Map for CurrentDependencies {
|
|
||||||
type Key = PackageId;
|
|
||||||
type Value = CurrentDependencyInfo;
|
|
||||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
|
||||||
Ok(key)
|
|
||||||
}
|
|
||||||
fn key_string(key: &Self::Key) -> Result<InternedString, Error> {
|
|
||||||
Ok(key.clone().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
#[model = "Model<Self>"]
|
|
||||||
pub struct StaticDependencyInfo {
|
|
||||||
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 {
|
|
||||||
#[serde(default)]
|
|
||||||
pub health_checks: BTreeSet<HealthCheckId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
|
||||||
pub struct InterfaceAddressMap(pub BTreeMap<HostId, InterfaceAddresses>);
|
|
||||||
impl Map for InterfaceAddressMap {
|
|
||||||
type Key = HostId;
|
|
||||||
type Value = InterfaceAddresses;
|
|
||||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
|
||||||
Ok(key)
|
|
||||||
}
|
|
||||||
fn key_string(key: &Self::Key) -> Result<InternedString, Error> {
|
|
||||||
Ok(key.clone().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
#[model = "Model<Self>"]
|
|
||||||
pub struct InterfaceAddresses {
|
|
||||||
pub tor_address: Option<String>,
|
|
||||||
pub lan_address: Option<String>,
|
|
||||||
}
|
|
||||||
48
core/startos/src/db/model/mod.rs
Normal file
48
core/startos/src/db/model/mod.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use patch_db::HasModel;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::account::AccountInfo;
|
||||||
|
use crate::auth::Sessions;
|
||||||
|
use crate::backup::target::cifs::CifsTargets;
|
||||||
|
use crate::db::model::private::Private;
|
||||||
|
use crate::db::model::public::Public;
|
||||||
|
use crate::net::forward::AvailablePorts;
|
||||||
|
use crate::net::keys::KeyStore;
|
||||||
|
use crate::notifications::Notifications;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::ssh::SshKeys;
|
||||||
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
|
pub mod package;
|
||||||
|
pub mod private;
|
||||||
|
pub mod public;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct Database {
|
||||||
|
pub public: Public,
|
||||||
|
pub private: Private,
|
||||||
|
}
|
||||||
|
impl Database {
|
||||||
|
pub fn init(account: &AccountInfo) -> Result<Self, Error> {
|
||||||
|
Ok(Self {
|
||||||
|
public: Public::init(account)?,
|
||||||
|
private: Private {
|
||||||
|
key_store: KeyStore::new(account)?,
|
||||||
|
password: account.password.clone(),
|
||||||
|
ssh_privkey: Pem(account.ssh_key.clone()),
|
||||||
|
ssh_pubkeys: SshKeys::new(),
|
||||||
|
available_ports: AvailablePorts::new(),
|
||||||
|
sessions: Sessions::new(),
|
||||||
|
notifications: Notifications::new(),
|
||||||
|
cifs: CifsTargets::new(),
|
||||||
|
package_stores: BTreeMap::new(),
|
||||||
|
}, // TODO
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DatabaseModel = Model<Database>;
|
||||||
424
core/startos/src/db/model/package.rs
Normal file
424
core/startos/src/db/model/package.rs
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use imbl_value::InternedString;
|
||||||
|
use models::{DataUrl, HealthCheckId, HostId, PackageId};
|
||||||
|
use patch_db::json_ptr::JsonPointer;
|
||||||
|
use patch_db::HasModel;
|
||||||
|
use reqwest::Url;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
use crate::net::host::HostInfo;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::progress::FullProgress;
|
||||||
|
use crate::s9pk::manifest::Manifest;
|
||||||
|
use crate::status::Status;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
|
pub struct AllPackageData(pub BTreeMap<PackageId, PackageDataEntry>);
|
||||||
|
impl Map for AllPackageData {
|
||||||
|
type Key = PackageId;
|
||||||
|
type Value = PackageDataEntry;
|
||||||
|
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
fn key_string(key: &Self::Key) -> Result<InternedString, Error> {
|
||||||
|
Ok(key.clone().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum ManifestPreference {
|
||||||
|
Old,
|
||||||
|
New,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[serde(tag = "state")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub enum PackageState {
|
||||||
|
Installing(InstallingState),
|
||||||
|
Restoring(InstallingState),
|
||||||
|
Updating(UpdatingState),
|
||||||
|
Installed(InstalledState),
|
||||||
|
Removing(InstalledState),
|
||||||
|
}
|
||||||
|
impl PackageState {
|
||||||
|
pub fn expect_installed(&self) -> Result<&InstalledState, Error> {
|
||||||
|
match self {
|
||||||
|
Self::Installed(a) => Ok(a),
|
||||||
|
a => Err(Error::new(
|
||||||
|
eyre!(
|
||||||
|
"Package {} is not in installed state",
|
||||||
|
self.as_manifest(ManifestPreference::Old).id
|
||||||
|
),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn into_installing_info(self) -> Option<InstallingInfo> {
|
||||||
|
match self {
|
||||||
|
Self::Installing(InstallingState { installing_info })
|
||||||
|
| Self::Restoring(InstallingState { installing_info }) => Some(installing_info),
|
||||||
|
Self::Updating(UpdatingState {
|
||||||
|
installing_info, ..
|
||||||
|
}) => Some(installing_info),
|
||||||
|
Self::Installed(_) | Self::Removing(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_installing_info(&self) -> Option<&InstallingInfo> {
|
||||||
|
match self {
|
||||||
|
Self::Installing(InstallingState { installing_info })
|
||||||
|
| Self::Restoring(InstallingState { installing_info }) => Some(installing_info),
|
||||||
|
Self::Updating(UpdatingState {
|
||||||
|
installing_info, ..
|
||||||
|
}) => Some(installing_info),
|
||||||
|
Self::Installed(_) | Self::Removing(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_installing_info_mut(&mut self) -> Option<&mut InstallingInfo> {
|
||||||
|
match self {
|
||||||
|
Self::Installing(InstallingState { installing_info })
|
||||||
|
| Self::Restoring(InstallingState { installing_info }) => Some(installing_info),
|
||||||
|
Self::Updating(UpdatingState {
|
||||||
|
installing_info, ..
|
||||||
|
}) => Some(installing_info),
|
||||||
|
Self::Installed(_) | Self::Removing(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn into_manifest(self, preference: ManifestPreference) -> Manifest {
|
||||||
|
match self {
|
||||||
|
Self::Installing(InstallingState {
|
||||||
|
installing_info: InstallingInfo { new_manifest, .. },
|
||||||
|
})
|
||||||
|
| Self::Restoring(InstallingState {
|
||||||
|
installing_info: InstallingInfo { new_manifest, .. },
|
||||||
|
}) => new_manifest,
|
||||||
|
Self::Updating(UpdatingState { manifest, .. })
|
||||||
|
if preference == ManifestPreference::Old =>
|
||||||
|
{
|
||||||
|
manifest
|
||||||
|
}
|
||||||
|
Self::Updating(UpdatingState {
|
||||||
|
installing_info: InstallingInfo { new_manifest, .. },
|
||||||
|
..
|
||||||
|
}) => new_manifest,
|
||||||
|
Self::Installed(InstalledState { manifest })
|
||||||
|
| Self::Removing(InstalledState { manifest }) => manifest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_manifest(&self, preference: ManifestPreference) -> &Manifest {
|
||||||
|
match self {
|
||||||
|
Self::Installing(InstallingState {
|
||||||
|
installing_info: InstallingInfo { new_manifest, .. },
|
||||||
|
})
|
||||||
|
| Self::Restoring(InstallingState {
|
||||||
|
installing_info: InstallingInfo { new_manifest, .. },
|
||||||
|
}) => new_manifest,
|
||||||
|
Self::Updating(UpdatingState { manifest, .. })
|
||||||
|
if preference == ManifestPreference::Old =>
|
||||||
|
{
|
||||||
|
manifest
|
||||||
|
}
|
||||||
|
Self::Updating(UpdatingState {
|
||||||
|
installing_info: InstallingInfo { new_manifest, .. },
|
||||||
|
..
|
||||||
|
}) => new_manifest,
|
||||||
|
Self::Installed(InstalledState { manifest })
|
||||||
|
| Self::Removing(InstalledState { manifest }) => manifest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_manifest_mut(&mut self, preference: ManifestPreference) -> &mut Manifest {
|
||||||
|
match self {
|
||||||
|
Self::Installing(InstallingState {
|
||||||
|
installing_info: InstallingInfo { new_manifest, .. },
|
||||||
|
})
|
||||||
|
| Self::Restoring(InstallingState {
|
||||||
|
installing_info: InstallingInfo { new_manifest, .. },
|
||||||
|
}) => new_manifest,
|
||||||
|
Self::Updating(UpdatingState { manifest, .. })
|
||||||
|
if preference == ManifestPreference::Old =>
|
||||||
|
{
|
||||||
|
manifest
|
||||||
|
}
|
||||||
|
Self::Updating(UpdatingState {
|
||||||
|
installing_info: InstallingInfo { new_manifest, .. },
|
||||||
|
..
|
||||||
|
}) => new_manifest,
|
||||||
|
Self::Installed(InstalledState { manifest })
|
||||||
|
| Self::Removing(InstalledState { manifest }) => manifest,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Model<PackageState> {
|
||||||
|
pub fn expect_installed(&self) -> Result<&Model<InstalledState>, Error> {
|
||||||
|
match self.as_match() {
|
||||||
|
PackageStateMatchModelRef::Installed(a) => Ok(a),
|
||||||
|
a => Err(Error::new(
|
||||||
|
eyre!(
|
||||||
|
"Package {} is not in installed state",
|
||||||
|
self.as_manifest(ManifestPreference::Old).as_id().de()?
|
||||||
|
),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn into_installing_info(self) -> Option<Model<InstallingInfo>> {
|
||||||
|
match self.into_match() {
|
||||||
|
PackageStateMatchModel::Installing(s) | PackageStateMatchModel::Restoring(s) => {
|
||||||
|
Some(s.into_installing_info())
|
||||||
|
}
|
||||||
|
PackageStateMatchModel::Updating(s) => Some(s.into_installing_info()),
|
||||||
|
PackageStateMatchModel::Installed(_) | PackageStateMatchModel::Removing(_) => None,
|
||||||
|
PackageStateMatchModel::Error(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_installing_info(&self) -> Option<&Model<InstallingInfo>> {
|
||||||
|
match self.as_match() {
|
||||||
|
PackageStateMatchModelRef::Installing(s) | PackageStateMatchModelRef::Restoring(s) => {
|
||||||
|
Some(s.as_installing_info())
|
||||||
|
}
|
||||||
|
PackageStateMatchModelRef::Updating(s) => Some(s.as_installing_info()),
|
||||||
|
PackageStateMatchModelRef::Installed(_) | PackageStateMatchModelRef::Removing(_) => {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
PackageStateMatchModelRef::Error(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_installing_info_mut(&mut self) -> Option<&mut Model<InstallingInfo>> {
|
||||||
|
match self.as_match_mut() {
|
||||||
|
PackageStateMatchModelMut::Installing(s) | PackageStateMatchModelMut::Restoring(s) => {
|
||||||
|
Some(s.as_installing_info_mut())
|
||||||
|
}
|
||||||
|
PackageStateMatchModelMut::Updating(s) => Some(s.as_installing_info_mut()),
|
||||||
|
PackageStateMatchModelMut::Installed(_) | PackageStateMatchModelMut::Removing(_) => {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
PackageStateMatchModelMut::Error(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn into_manifest(self, preference: ManifestPreference) -> Model<Manifest> {
|
||||||
|
match self.into_match() {
|
||||||
|
PackageStateMatchModel::Installing(s) | PackageStateMatchModel::Restoring(s) => {
|
||||||
|
s.into_installing_info().into_new_manifest()
|
||||||
|
}
|
||||||
|
PackageStateMatchModel::Updating(s) if preference == ManifestPreference::Old => {
|
||||||
|
s.into_manifest()
|
||||||
|
}
|
||||||
|
PackageStateMatchModel::Updating(s) => s.into_installing_info().into_new_manifest(),
|
||||||
|
PackageStateMatchModel::Installed(s) | PackageStateMatchModel::Removing(s) => {
|
||||||
|
s.into_manifest()
|
||||||
|
}
|
||||||
|
PackageStateMatchModel::Error(_) => Value::Null.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_manifest(&self, preference: ManifestPreference) -> &Model<Manifest> {
|
||||||
|
match self.as_match() {
|
||||||
|
PackageStateMatchModelRef::Installing(s) | PackageStateMatchModelRef::Restoring(s) => {
|
||||||
|
s.as_installing_info().as_new_manifest()
|
||||||
|
}
|
||||||
|
PackageStateMatchModelRef::Updating(s) if preference == ManifestPreference::Old => {
|
||||||
|
s.as_manifest()
|
||||||
|
}
|
||||||
|
PackageStateMatchModelRef::Updating(s) => s.as_installing_info().as_new_manifest(),
|
||||||
|
PackageStateMatchModelRef::Installed(s) | PackageStateMatchModelRef::Removing(s) => {
|
||||||
|
s.as_manifest()
|
||||||
|
}
|
||||||
|
PackageStateMatchModelRef::Error(_) => (&Value::Null).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn as_manifest_mut(
|
||||||
|
&mut self,
|
||||||
|
preference: ManifestPreference,
|
||||||
|
) -> Result<&mut Model<Manifest>, Error> {
|
||||||
|
Ok(match self.as_match_mut() {
|
||||||
|
PackageStateMatchModelMut::Installing(s) | PackageStateMatchModelMut::Restoring(s) => {
|
||||||
|
s.as_installing_info_mut().as_new_manifest_mut()
|
||||||
|
}
|
||||||
|
PackageStateMatchModelMut::Updating(s) if preference == ManifestPreference::Old => {
|
||||||
|
s.as_manifest_mut()
|
||||||
|
}
|
||||||
|
PackageStateMatchModelMut::Updating(s) => {
|
||||||
|
s.as_installing_info_mut().as_new_manifest_mut()
|
||||||
|
}
|
||||||
|
PackageStateMatchModelMut::Installed(s) | PackageStateMatchModelMut::Removing(s) => {
|
||||||
|
s.as_manifest_mut()
|
||||||
|
}
|
||||||
|
PackageStateMatchModelMut::Error(s) => {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("could not determine package state to get manifest"),
|
||||||
|
ErrorKind::Database,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct InstallingState {
|
||||||
|
pub installing_info: InstallingInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct UpdatingState {
|
||||||
|
pub manifest: Manifest,
|
||||||
|
pub installing_info: InstallingInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct InstalledState {
|
||||||
|
pub manifest: Manifest,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct InstallingInfo {
|
||||||
|
pub new_manifest: Manifest,
|
||||||
|
pub progress: FullProgress,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct PackageDataEntry {
|
||||||
|
pub state_info: PackageState,
|
||||||
|
pub status: Status,
|
||||||
|
pub marketplace_url: Option<Url>,
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(with = "crate::util::serde::ed25519_pubkey")]
|
||||||
|
pub developer_key: ed25519_dalek::VerifyingKey,
|
||||||
|
pub icon: DataUrl<'static>,
|
||||||
|
pub last_backup: Option<DateTime<Utc>>,
|
||||||
|
pub dependency_info: BTreeMap<PackageId, StaticDependencyInfo>,
|
||||||
|
pub current_dependents: CurrentDependents,
|
||||||
|
pub current_dependencies: CurrentDependencies,
|
||||||
|
pub interface_addresses: InterfaceAddressMap,
|
||||||
|
pub hosts: HostInfo,
|
||||||
|
pub store_exposed_ui: Vec<ExposedUI>,
|
||||||
|
pub store_exposed_dependents: Vec<JsonPointer>,
|
||||||
|
}
|
||||||
|
impl AsRef<PackageDataEntry> for PackageDataEntry {
|
||||||
|
fn as_ref(&self) -> &PackageDataEntry {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct ExposedDependent {
|
||||||
|
path: String,
|
||||||
|
title: String,
|
||||||
|
description: Option<String>,
|
||||||
|
masked: Option<bool>,
|
||||||
|
copyable: Option<bool>,
|
||||||
|
qr: Option<bool>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, TS)]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct ExposedUI {
|
||||||
|
#[ts(type = "string")]
|
||||||
|
pub path: JsonPointer,
|
||||||
|
pub title: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub masked: Option<bool>,
|
||||||
|
pub copyable: Option<bool>,
|
||||||
|
pub qr: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||||
|
pub struct CurrentDependents(pub BTreeMap<PackageId, CurrentDependencyInfo>);
|
||||||
|
impl CurrentDependents {
|
||||||
|
pub fn map(
|
||||||
|
mut self,
|
||||||
|
transform: impl Fn(
|
||||||
|
BTreeMap<PackageId, CurrentDependencyInfo>,
|
||||||
|
) -> BTreeMap<PackageId, CurrentDependencyInfo>,
|
||||||
|
) -> Self {
|
||||||
|
self.0 = transform(self.0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Map for CurrentDependents {
|
||||||
|
type Key = PackageId;
|
||||||
|
type Value = CurrentDependencyInfo;
|
||||||
|
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
fn key_string(key: &Self::Key) -> Result<InternedString, Error> {
|
||||||
|
Ok(key.clone().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||||
|
pub struct CurrentDependencies(pub BTreeMap<PackageId, CurrentDependencyInfo>);
|
||||||
|
impl CurrentDependencies {
|
||||||
|
pub fn map(
|
||||||
|
mut self,
|
||||||
|
transform: impl Fn(
|
||||||
|
BTreeMap<PackageId, CurrentDependencyInfo>,
|
||||||
|
) -> BTreeMap<PackageId, CurrentDependencyInfo>,
|
||||||
|
) -> Self {
|
||||||
|
self.0 = transform(self.0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Map for CurrentDependencies {
|
||||||
|
type Key = PackageId;
|
||||||
|
type Value = CurrentDependencyInfo;
|
||||||
|
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
fn key_string(key: &Self::Key) -> Result<InternedString, Error> {
|
||||||
|
Ok(key.clone().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct StaticDependencyInfo {
|
||||||
|
pub title: String,
|
||||||
|
pub icon: DataUrl<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[serde(tag = "kind")]
|
||||||
|
pub enum CurrentDependencyInfo {
|
||||||
|
Exists,
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
Running {
|
||||||
|
#[serde(default)]
|
||||||
|
health_checks: BTreeSet<HealthCheckId>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
|
pub struct InterfaceAddressMap(pub BTreeMap<HostId, InterfaceAddresses>);
|
||||||
|
impl Map for InterfaceAddressMap {
|
||||||
|
type Key = HostId;
|
||||||
|
type Value = InterfaceAddresses;
|
||||||
|
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
fn key_string(key: &Self::Key) -> Result<InternedString, Error> {
|
||||||
|
Ok(key.clone().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct InterfaceAddresses {
|
||||||
|
pub tor_address: Option<String>,
|
||||||
|
pub lan_address: Option<String>,
|
||||||
|
}
|
||||||
30
core/startos/src/db/model/private.rs
Normal file
30
core/startos/src/db/model/private.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use models::PackageId;
|
||||||
|
use patch_db::{HasModel, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::auth::Sessions;
|
||||||
|
use crate::backup::target::cifs::CifsTargets;
|
||||||
|
use crate::net::forward::AvailablePorts;
|
||||||
|
use crate::net::keys::KeyStore;
|
||||||
|
use crate::notifications::Notifications;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::ssh::SshKeys;
|
||||||
|
use crate::util::serde::Pem;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct Private {
|
||||||
|
pub key_store: KeyStore,
|
||||||
|
pub password: String, // argon2 hash
|
||||||
|
pub ssh_privkey: Pem<ssh_key::PrivateKey>,
|
||||||
|
pub ssh_pubkeys: SshKeys,
|
||||||
|
pub available_ports: AvailablePorts,
|
||||||
|
pub sessions: Sessions,
|
||||||
|
pub notifications: Notifications,
|
||||||
|
pub cifs: CifsTargets,
|
||||||
|
#[serde(default)]
|
||||||
|
pub package_stores: BTreeMap<PackageId, Value>,
|
||||||
|
}
|
||||||
210
core/startos/src/db/model/public.rs
Normal file
210
core/startos/src/db/model/public.rs
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use emver::VersionRange;
|
||||||
|
use imbl_value::InternedString;
|
||||||
|
use ipnet::{Ipv4Net, Ipv6Net};
|
||||||
|
use isocountry::CountryCode;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use models::PackageId;
|
||||||
|
use openssl::hash::MessageDigest;
|
||||||
|
use patch_db::{HasModel, Value};
|
||||||
|
use reqwest::Url;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use torut::onion::OnionAddressV3;
|
||||||
|
|
||||||
|
use crate::account::AccountInfo;
|
||||||
|
use crate::db::model::package::AllPackageData;
|
||||||
|
use crate::net::utils::{get_iface_ipv4_addr, get_iface_ipv6_addr};
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::util::cpupower::Governor;
|
||||||
|
use crate::util::Version;
|
||||||
|
use crate::version::{Current, VersionT};
|
||||||
|
use crate::{ARCH, PLATFORM};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
// #[macro_debug]
|
||||||
|
pub struct Public {
|
||||||
|
pub server_info: ServerInfo,
|
||||||
|
pub package_data: AllPackageData,
|
||||||
|
pub ui: Value,
|
||||||
|
}
|
||||||
|
impl Public {
|
||||||
|
pub fn init(account: &AccountInfo) -> Result<Self, Error> {
|
||||||
|
let lan_address = account.hostname.lan_address().parse().unwrap();
|
||||||
|
Ok(Self {
|
||||||
|
server_info: ServerInfo {
|
||||||
|
arch: get_arch(),
|
||||||
|
platform: get_platform(),
|
||||||
|
id: account.server_id.clone(),
|
||||||
|
version: Current::new().semver().into(),
|
||||||
|
hostname: account.hostname.no_dot_host_name(),
|
||||||
|
last_backup: None,
|
||||||
|
last_wifi_region: None,
|
||||||
|
eos_version_compat: Current::new().compat().clone(),
|
||||||
|
lan_address,
|
||||||
|
onion_address: account.tor_key.public().get_onion_address(),
|
||||||
|
tor_address: format!("https://{}", account.tor_key.public().get_onion_address())
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
ip_info: BTreeMap::new(),
|
||||||
|
status_info: ServerStatus {
|
||||||
|
backup_progress: None,
|
||||||
|
updated: false,
|
||||||
|
update_progress: None,
|
||||||
|
shutting_down: false,
|
||||||
|
restarting: false,
|
||||||
|
},
|
||||||
|
wifi: WifiInfo {
|
||||||
|
ssids: Vec::new(),
|
||||||
|
connected: None,
|
||||||
|
selected: None,
|
||||||
|
},
|
||||||
|
unread_notification_count: 0,
|
||||||
|
connection_addresses: ConnectionAddresses {
|
||||||
|
tor: Vec::new(),
|
||||||
|
clearnet: Vec::new(),
|
||||||
|
},
|
||||||
|
password_hash: account.password.clone(),
|
||||||
|
pubkey: ssh_key::PublicKey::from(&account.ssh_key)
|
||||||
|
.to_openssh()
|
||||||
|
.unwrap(),
|
||||||
|
ca_fingerprint: account
|
||||||
|
.root_ca_cert
|
||||||
|
.digest(MessageDigest::sha256())
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|x| format!("{x:X}"))
|
||||||
|
.join(":"),
|
||||||
|
ntp_synced: false,
|
||||||
|
zram: true,
|
||||||
|
governor: None,
|
||||||
|
},
|
||||||
|
package_data: AllPackageData::default(),
|
||||||
|
ui: serde_json::from_str(include_str!(concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/../../web/patchdb-ui-seed.json"
|
||||||
|
)))
|
||||||
|
.with_kind(ErrorKind::Deserialization)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_arch() -> InternedString {
|
||||||
|
(*ARCH).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_platform() -> InternedString {
|
||||||
|
(&*PLATFORM).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct ServerInfo {
|
||||||
|
#[serde(default = "get_arch")]
|
||||||
|
pub arch: InternedString,
|
||||||
|
#[serde(default = "get_platform")]
|
||||||
|
pub platform: InternedString,
|
||||||
|
pub id: String,
|
||||||
|
pub hostname: String,
|
||||||
|
pub version: Version,
|
||||||
|
pub last_backup: Option<DateTime<Utc>>,
|
||||||
|
/// Used in the wifi to determine the region to set the system to
|
||||||
|
pub last_wifi_region: Option<CountryCode>,
|
||||||
|
pub eos_version_compat: VersionRange,
|
||||||
|
pub lan_address: Url,
|
||||||
|
pub onion_address: OnionAddressV3,
|
||||||
|
/// for backwards compatibility
|
||||||
|
pub tor_address: Url,
|
||||||
|
pub ip_info: BTreeMap<String, IpInfo>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub status_info: ServerStatus,
|
||||||
|
pub wifi: WifiInfo,
|
||||||
|
pub unread_notification_count: u64,
|
||||||
|
pub connection_addresses: ConnectionAddresses,
|
||||||
|
pub password_hash: String,
|
||||||
|
pub pubkey: String,
|
||||||
|
pub ca_fingerprint: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub ntp_synced: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub zram: bool,
|
||||||
|
pub governor: Option<Governor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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>,
|
||||||
|
pub ipv6_range: Option<Ipv6Net>,
|
||||||
|
pub ipv6: Option<Ipv6Addr>,
|
||||||
|
}
|
||||||
|
impl IpInfo {
|
||||||
|
pub async fn for_interface(iface: &str) -> Result<Self, Error> {
|
||||||
|
let (ipv4, ipv4_range) = get_iface_ipv4_addr(iface).await?.unzip();
|
||||||
|
let (ipv6, ipv6_range) = get_iface_ipv6_addr(iface).await?.unzip();
|
||||||
|
Ok(Self {
|
||||||
|
ipv4_range,
|
||||||
|
ipv4,
|
||||||
|
ipv6_range,
|
||||||
|
ipv6,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
pub backup_progress: Option<BTreeMap<PackageId, BackupProgress>>,
|
||||||
|
pub updated: bool,
|
||||||
|
pub update_progress: Option<UpdateProgress>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub shutting_down: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub restarting: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct UpdateProgress {
|
||||||
|
pub size: Option<u64>,
|
||||||
|
pub downloaded: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
|
pub struct WifiInfo {
|
||||||
|
pub ssids: Vec<String>,
|
||||||
|
pub selected: Option<String>,
|
||||||
|
pub connected: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct ServerSpecs {
|
||||||
|
pub cpu: String,
|
||||||
|
pub disk: String,
|
||||||
|
pub memory: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct ConnectionAddresses {
|
||||||
|
pub tor: Vec<String>,
|
||||||
|
pub clearnet: Vec<String>,
|
||||||
|
}
|
||||||
@@ -124,6 +124,12 @@ impl<T: Serialize + DeserializeOwned> Model<T> {
|
|||||||
self.ser(&orig)?;
|
self.ser(&orig)?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
pub fn map_mutate(&mut self, f: impl FnOnce(T) -> Result<T, Error>) -> Result<T, Error> {
|
||||||
|
let mut orig = self.de()?;
|
||||||
|
let res = f(orig)?;
|
||||||
|
self.ser(&res)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<T> Clone for Model<T> {
|
impl<T> Clone for Model<T> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ use tracing::instrument;
|
|||||||
|
|
||||||
use crate::config::{Config, ConfigSpec, ConfigureContext};
|
use crate::config::{Config, ConfigSpec, ConfigureContext};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::db::model::{CurrentDependencies, Database};
|
use crate::db::model::package::CurrentDependencies;
|
||||||
|
use crate::db::model::Database;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::s9pk::manifest::Manifest;
|
use crate::s9pk::manifest::Manifest;
|
||||||
use crate::status::DependencyConfigErrors;
|
use crate::status::DependencyConfigErrors;
|
||||||
@@ -195,52 +196,19 @@ pub async fn configure_logic(
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
|
||||||
pub fn add_dependent_to_current_dependents_lists(
|
|
||||||
db: &mut Model<Database>,
|
|
||||||
dependent_id: &PackageId,
|
|
||||||
current_dependencies: &CurrentDependencies,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
for (dependency, dep_info) in ¤t_dependencies.0 {
|
|
||||||
if let Some(dependency_dependents) = db
|
|
||||||
.as_public_mut()
|
|
||||||
.as_package_data_mut()
|
|
||||||
.as_idx_mut(dependency)
|
|
||||||
.and_then(|pde| pde.as_installed_mut())
|
|
||||||
.map(|i| i.as_current_dependents_mut())
|
|
||||||
{
|
|
||||||
dependency_dependents.insert(dependent_id, dep_info)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn compute_dependency_config_errs(
|
pub async fn compute_dependency_config_errs(
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
db: &Peeked,
|
db: &Peeked,
|
||||||
manifest: &Manifest,
|
id: &PackageId,
|
||||||
current_dependencies: &CurrentDependencies,
|
current_dependencies: &CurrentDependencies,
|
||||||
dependency_config: &BTreeMap<PackageId, Config>,
|
dependency_config: &BTreeMap<PackageId, Config>,
|
||||||
) -> Result<DependencyConfigErrors, Error> {
|
) -> Result<DependencyConfigErrors, Error> {
|
||||||
let mut dependency_config_errs = BTreeMap::new();
|
let mut dependency_config_errs = BTreeMap::new();
|
||||||
for (dependency, _dep_info) in current_dependencies
|
for (dependency, _dep_info) in current_dependencies.0.iter() {
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.filter(|(dep_id, _)| dep_id != &&manifest.id)
|
|
||||||
{
|
|
||||||
// check if config passes dependency check
|
// check if config passes dependency check
|
||||||
if let Some(cfg) = &manifest
|
if let Some(error) = todo!() {
|
||||||
.dependencies
|
dependency_config_errs.insert(dependency.clone(), error);
|
||||||
.0
|
|
||||||
.get(dependency)
|
|
||||||
.or_not_found(dependency)?
|
|
||||||
.config
|
|
||||||
{
|
|
||||||
let error = todo!();
|
|
||||||
{
|
|
||||||
dependency_config_errs.insert(dependency.clone(), error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(DependencyConfigErrors(dependency_config_errs))
|
Ok(DependencyConfigErrors(dependency_config_errs))
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use tracing::instrument;
|
|||||||
|
|
||||||
use crate::account::AccountInfo;
|
use crate::account::AccountInfo;
|
||||||
use crate::context::config::ServerConfig;
|
use crate::context::config::ServerConfig;
|
||||||
use crate::db::model::ServerStatus;
|
use crate::db::model::public::ServerStatus;
|
||||||
use crate::disk::mount::util::unmount;
|
use crate::disk::mount::util::unmount;
|
||||||
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
use crate::middleware::auth::LOCAL_AUTH_COOKIE_PATH;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|||||||
@@ -18,10 +18,7 @@ use tracing::instrument;
|
|||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::core::rpc_continuations::{RequestGuid, RpcContinuation};
|
use crate::core::rpc_continuations::{RequestGuid, RpcContinuation};
|
||||||
use crate::db::model::{
|
use crate::db::model::package::{ManifestPreference, PackageState, PackageStateMatchModelRef};
|
||||||
PackageDataEntry, PackageDataEntryInstalled, PackageDataEntryMatchModelRef,
|
|
||||||
PackageDataEntryRemoving,
|
|
||||||
};
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{FullProgress, PhasedProgressBar};
|
use crate::progress::{FullProgress, PhasedProgressBar};
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
@@ -40,27 +37,27 @@ pub async fn list(ctx: RpcContext) -> Result<Value, Error> {
|
|||||||
Ok(ctx.db.peek().await.as_public().as_package_data().as_entries()?
|
Ok(ctx.db.peek().await.as_public().as_package_data().as_entries()?
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(id, pde)| {
|
.filter_map(|(id, pde)| {
|
||||||
let status = match pde.as_match() {
|
let status = match pde.as_state_info().as_match() {
|
||||||
PackageDataEntryMatchModelRef::Installed(_) => {
|
PackageStateMatchModelRef::Installed(_) => {
|
||||||
"installed"
|
"installed"
|
||||||
}
|
}
|
||||||
PackageDataEntryMatchModelRef::Installing(_) => {
|
PackageStateMatchModelRef::Installing(_) => {
|
||||||
"installing"
|
"installing"
|
||||||
}
|
}
|
||||||
PackageDataEntryMatchModelRef::Updating(_) => {
|
PackageStateMatchModelRef::Updating(_) => {
|
||||||
"updating"
|
"updating"
|
||||||
}
|
}
|
||||||
PackageDataEntryMatchModelRef::Restoring(_) => {
|
PackageStateMatchModelRef::Restoring(_) => {
|
||||||
"restoring"
|
"restoring"
|
||||||
}
|
}
|
||||||
PackageDataEntryMatchModelRef::Removing(_) => {
|
PackageStateMatchModelRef::Removing(_) => {
|
||||||
"removing"
|
"removing"
|
||||||
}
|
}
|
||||||
PackageDataEntryMatchModelRef::Error(_) => {
|
PackageStateMatchModelRef::Error(_) => {
|
||||||
"error"
|
"error"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
serde_json::to_value(json!({ "status":status, "id": id.clone(), "version": pde.as_manifest().as_version().de().ok()?}))
|
serde_json::to_value(json!({ "status": status, "id": id.clone(), "version": pde.as_state_info().as_manifest(ManifestPreference::Old).as_version().de().ok()?}))
|
||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
@@ -212,7 +209,7 @@ pub async fn sideload(ctx: RpcContext) -> Result<SideloadResponse, Error> {
|
|||||||
.as_public()
|
.as_public()
|
||||||
.as_package_data()
|
.as_package_data()
|
||||||
.as_idx(&id)
|
.as_idx(&id)
|
||||||
.and_then(|e| e.as_install_progress())
|
.and_then(|e| e.as_state_info().as_installing_info()).map(|i| i.as_progress())
|
||||||
{
|
{
|
||||||
Ok::<_, ()>(p.de()?)
|
Ok::<_, ()>(p.de()?)
|
||||||
} else {
|
} else {
|
||||||
@@ -407,31 +404,18 @@ pub async fn uninstall(
|
|||||||
) -> Result<PackageId, Error> {
|
) -> Result<PackageId, Error> {
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
let (manifest, static_files, installed) = match db
|
let entry = db
|
||||||
.as_public()
|
.as_public_mut()
|
||||||
.as_package_data()
|
.as_package_data_mut()
|
||||||
.as_idx(&id)
|
.as_idx_mut(&id)
|
||||||
.or_not_found(&id)?
|
.or_not_found(&id)?;
|
||||||
.de()?
|
entry.as_state_info_mut().map_mutate(|s| match s {
|
||||||
{
|
PackageState::Installed(s) => Ok(PackageState::Removing(s)),
|
||||||
PackageDataEntry::Installed(PackageDataEntryInstalled {
|
_ => Err(Error::new(
|
||||||
manifest,
|
eyre!("Package {id} is not installed."),
|
||||||
static_files,
|
crate::ErrorKind::NotFound,
|
||||||
installed,
|
)),
|
||||||
}) => (manifest, static_files, installed),
|
})
|
||||||
_ => {
|
|
||||||
return Err(Error::new(
|
|
||||||
eyre!("Package is not installed."),
|
|
||||||
crate::ErrorKind::NotFound,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let pde = PackageDataEntry::Removing(PackageDataEntryRemoving {
|
|
||||||
manifest,
|
|
||||||
static_files,
|
|
||||||
removing: installed,
|
|
||||||
});
|
|
||||||
db.as_public_mut().as_package_data_mut().insert(&id, &pde)
|
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::db::model::IpInfo;
|
use crate::db::model::public::IpInfo;
|
||||||
use crate::net::utils::{iface_is_physical, list_interfaces};
|
use crate::net::utils::{iface_is_physical, list_interfaces};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|||||||
@@ -205,8 +205,6 @@ impl NetService {
|
|||||||
.as_package_data_mut()
|
.as_package_data_mut()
|
||||||
.as_idx_mut(pkg_id)
|
.as_idx_mut(pkg_id)
|
||||||
.or_not_found(pkg_id)?
|
.or_not_found(pkg_id)?
|
||||||
.as_installed_mut()
|
|
||||||
.or_not_found(pkg_id)?
|
|
||||||
.as_hosts_mut();
|
.as_hosts_mut();
|
||||||
hosts.add_binding(&mut ports, kind, &id, internal_port, options)?;
|
hosts.add_binding(&mut ports, kind, &id, internal_port, options)?;
|
||||||
let host = hosts
|
let host = hosts
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ use models::PackageId;
|
|||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::context::RpcContext;
|
||||||
|
use crate::db::model::package::ExposedUI;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use crate::{context::RpcContext, db::model::ExposedUI};
|
|
||||||
|
|
||||||
pub fn display_properties(response: Value) {
|
pub fn display_properties(response: Value) {
|
||||||
println!("{}", response);
|
println!("{}", response);
|
||||||
@@ -59,8 +60,6 @@ pub async fn properties(
|
|||||||
.as_package_data()
|
.as_package_data()
|
||||||
.as_idx(&id)
|
.as_idx(&id)
|
||||||
.or_not_found(&id)?
|
.or_not_found(&id)?
|
||||||
.as_installed()
|
|
||||||
.or_not_found(&id)?
|
|
||||||
.as_store_exposed_ui()
|
.as_store_exposed_ui()
|
||||||
.de()?
|
.de()?
|
||||||
.into_properties(&data))
|
.into_properties(&data))
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ use crate::action::ActionResult;
|
|||||||
use crate::config::action::ConfigRes;
|
use crate::config::action::ConfigRes;
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::core::rpc_continuations::RequestGuid;
|
use crate::core::rpc_continuations::RequestGuid;
|
||||||
use crate::db::model::{
|
use crate::db::model::package::{
|
||||||
InstalledPackageInfo, PackageDataEntry, PackageDataEntryInstalled, PackageDataEntryMatchModel,
|
InstalledState, PackageDataEntry, PackageState, PackageStateMatchModelRef, UpdatingState,
|
||||||
StaticFiles,
|
|
||||||
};
|
};
|
||||||
use crate::disk::mount::guard::GenericMountGuard;
|
use crate::disk::mount::guard::GenericMountGuard;
|
||||||
use crate::install::PKG_ARCHIVE_DIR;
|
use crate::install::PKG_ARCHIVE_DIR;
|
||||||
@@ -28,7 +27,7 @@ use crate::s9pk::S9pk;
|
|||||||
use crate::service::service_map::InstallProgressHandles;
|
use crate::service::service_map::InstallProgressHandles;
|
||||||
use crate::service::transition::TransitionKind;
|
use crate::service::transition::TransitionKind;
|
||||||
use crate::status::health_check::HealthCheckResult;
|
use crate::status::health_check::HealthCheckResult;
|
||||||
use crate::status::{MainStatus, Status};
|
use crate::status::MainStatus;
|
||||||
use crate::util::actor::{Actor, BackgroundJobs, SimpleActor};
|
use crate::util::actor::{Actor, BackgroundJobs, SimpleActor};
|
||||||
use crate::volume::data_dir;
|
use crate::volume::data_dir;
|
||||||
|
|
||||||
@@ -100,7 +99,7 @@ impl Service {
|
|||||||
) -> Result<Option<Self>, Error> {
|
) -> Result<Option<Self>, Error> {
|
||||||
let handle_installed = {
|
let handle_installed = {
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
move |s9pk: S9pk, i: Model<InstalledPackageInfo>| async move {
|
move |s9pk: S9pk, i: Model<PackageDataEntry>| async move {
|
||||||
for volume_id in &s9pk.as_manifest().volumes {
|
for volume_id in &s9pk.as_manifest().volumes {
|
||||||
let tmp_path =
|
let tmp_path =
|
||||||
data_dir(&ctx.datadir, &s9pk.as_manifest().id.clone(), volume_id);
|
data_dir(&ctx.datadir, &s9pk.as_manifest().id.clone(), volume_id);
|
||||||
@@ -118,16 +117,18 @@ impl Service {
|
|||||||
};
|
};
|
||||||
let s9pk_dir = ctx.datadir.join(PKG_ARCHIVE_DIR).join("installed"); // TODO: make this based on hash
|
let s9pk_dir = ctx.datadir.join(PKG_ARCHIVE_DIR).join("installed"); // TODO: make this based on hash
|
||||||
let s9pk_path = s9pk_dir.join(id).with_extension("s9pk");
|
let s9pk_path = s9pk_dir.join(id).with_extension("s9pk");
|
||||||
match ctx
|
let Some(entry) = ctx
|
||||||
.db
|
.db
|
||||||
.peek()
|
.peek()
|
||||||
.await
|
.await
|
||||||
.into_public()
|
.into_public()
|
||||||
.into_package_data()
|
.into_package_data()
|
||||||
.into_idx(id)
|
.into_idx(id)
|
||||||
.map(|pde| pde.into_match())
|
else {
|
||||||
{
|
return Ok(None);
|
||||||
Some(PackageDataEntryMatchModel::Installing(_)) => {
|
};
|
||||||
|
match entry.as_state_info().as_match() {
|
||||||
|
PackageStateMatchModelRef::Installing(_) => {
|
||||||
if disposition == LoadDisposition::Retry {
|
if disposition == LoadDisposition::Retry {
|
||||||
if let Ok(s9pk) = S9pk::open(s9pk_path, Some(id)).await.map_err(|e| {
|
if let Ok(s9pk) = S9pk::open(s9pk_path, Some(id)).await.map_err(|e| {
|
||||||
tracing::error!("Error opening s9pk for install: {e}");
|
tracing::error!("Error opening s9pk for install: {e}");
|
||||||
@@ -150,14 +151,17 @@ impl Service {
|
|||||||
.await?;
|
.await?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
Some(PackageDataEntryMatchModel::Updating(e)) => {
|
PackageStateMatchModelRef::Updating(s) => {
|
||||||
if disposition == LoadDisposition::Retry
|
if disposition == LoadDisposition::Retry
|
||||||
&& e.as_install_progress().de()?.phases.iter().any(
|
&& s.as_installing_info()
|
||||||
|NamedProgress { name, progress }| {
|
.as_progress()
|
||||||
|
.de()?
|
||||||
|
.phases
|
||||||
|
.iter()
|
||||||
|
.any(|NamedProgress { name, progress }| {
|
||||||
name.eq_ignore_ascii_case("download")
|
name.eq_ignore_ascii_case("download")
|
||||||
&& progress == &Progress::Complete(true)
|
&& progress == &Progress::Complete(true)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
{
|
{
|
||||||
if let Ok(s9pk) = S9pk::open(&s9pk_path, Some(id)).await.map_err(|e| {
|
if let Ok(s9pk) = S9pk::open(&s9pk_path, Some(id)).await.map_err(|e| {
|
||||||
tracing::error!("Error opening s9pk for update: {e}");
|
tracing::error!("Error opening s9pk for update: {e}");
|
||||||
@@ -166,7 +170,7 @@ impl Service {
|
|||||||
if let Ok(service) = Self::install(
|
if let Ok(service) = Self::install(
|
||||||
ctx.clone(),
|
ctx.clone(),
|
||||||
s9pk,
|
s9pk,
|
||||||
Some(e.as_installed().as_manifest().as_version().de()?),
|
Some(s.as_manifest().as_version().de()?),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -181,24 +185,28 @@ impl Service {
|
|||||||
let s9pk = S9pk::open(s9pk_path, Some(id)).await?;
|
let s9pk = S9pk::open(s9pk_path, Some(id)).await?;
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate({
|
.mutate({
|
||||||
let manifest = s9pk.as_manifest().clone();
|
|
||||||
|db| {
|
|db| {
|
||||||
db.as_public_mut()
|
db.as_public_mut()
|
||||||
.as_package_data_mut()
|
.as_package_data_mut()
|
||||||
.as_idx_mut(&manifest.id)
|
.as_idx_mut(&id)
|
||||||
.or_not_found(&manifest.id)?
|
.or_not_found(&id)?
|
||||||
.ser(&PackageDataEntry::Installed(PackageDataEntryInstalled {
|
.as_state_info_mut()
|
||||||
static_files: e.as_static_files().de()?,
|
.map_mutate(|s| {
|
||||||
manifest,
|
if let PackageState::Updating(UpdatingState {
|
||||||
installed: e.as_installed().de()?,
|
manifest, ..
|
||||||
}))
|
}) = s
|
||||||
|
{
|
||||||
|
Ok(PackageState::Installed(InstalledState { manifest }))
|
||||||
|
} else {
|
||||||
|
Err(Error::new(eyre!("Race condition detected - package state changed during load"), ErrorKind::Database))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
handle_installed(s9pk, e.as_installed().clone()).await
|
handle_installed(s9pk, entry).await
|
||||||
}
|
}
|
||||||
Some(PackageDataEntryMatchModel::Removing(_))
|
PackageStateMatchModelRef::Removing(_) | PackageStateMatchModelRef::Restoring(_) => {
|
||||||
| Some(PackageDataEntryMatchModel::Restoring(_)) => {
|
|
||||||
if let Ok(s9pk) = S9pk::open(s9pk_path, Some(id)).await.map_err(|e| {
|
if let Ok(s9pk) = S9pk::open(s9pk_path, Some(id)).await.map_err(|e| {
|
||||||
tracing::error!("Error opening s9pk for removal: {e}");
|
tracing::error!("Error opening s9pk for removal: {e}");
|
||||||
tracing::debug!("{e:?}")
|
tracing::debug!("{e:?}")
|
||||||
@@ -230,18 +238,13 @@ impl Service {
|
|||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
Some(PackageDataEntryMatchModel::Installed(i)) => {
|
PackageStateMatchModelRef::Installed(_) => {
|
||||||
handle_installed(
|
handle_installed(S9pk::open(s9pk_path, Some(id)).await?, entry).await
|
||||||
S9pk::open(s9pk_path, Some(id)).await?,
|
|
||||||
i.as_installed().clone(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
Some(PackageDataEntryMatchModel::Error(e)) => Err(Error::new(
|
PackageStateMatchModelRef::Error(e) => Err(Error::new(
|
||||||
eyre!("Failed to parse PackageDataEntry, found {e:?}"),
|
eyre!("Failed to parse PackageDataEntry, found {e:?}"),
|
||||||
ErrorKind::Deserialization,
|
ErrorKind::Deserialization,
|
||||||
)),
|
)),
|
||||||
None => Ok(None),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +258,6 @@ impl Service {
|
|||||||
let manifest = s9pk.as_manifest().clone();
|
let manifest = s9pk.as_manifest().clone();
|
||||||
let developer_key = s9pk.as_archive().signer();
|
let developer_key = s9pk.as_archive().signer();
|
||||||
let icon = s9pk.icon_data_url().await?;
|
let icon = s9pk.icon_data_url().await?;
|
||||||
let static_files = StaticFiles::local(&manifest.id, &manifest.version, icon);
|
|
||||||
let service = Self::new(ctx.clone(), s9pk, StartStop::Stop).await?;
|
let service = Self::new(ctx.clone(), s9pk, StartStop::Stop).await?;
|
||||||
service
|
service
|
||||||
.seed
|
.seed
|
||||||
@@ -270,32 +272,19 @@ impl Service {
|
|||||||
}
|
}
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|d| {
|
.mutate(|d| {
|
||||||
d.as_public_mut()
|
let entry = d
|
||||||
|
.as_public_mut()
|
||||||
.as_package_data_mut()
|
.as_package_data_mut()
|
||||||
.as_idx_mut(&manifest.id)
|
.as_idx_mut(&manifest.id)
|
||||||
.or_not_found(&manifest.id)?
|
.or_not_found(&manifest.id)?;
|
||||||
.ser(&PackageDataEntry::Installed(PackageDataEntryInstalled {
|
entry
|
||||||
installed: InstalledPackageInfo {
|
.as_state_info_mut()
|
||||||
current_dependencies: Default::default(), // TODO
|
.ser(&PackageState::Installed(InstalledState { manifest }))?;
|
||||||
current_dependents: Default::default(), // TODO
|
entry.as_developer_key_mut().ser(&developer_key)?;
|
||||||
dependency_info: Default::default(), // TODO
|
entry.as_icon_mut().ser(&icon)?;
|
||||||
developer_key,
|
// TODO: marketplace url
|
||||||
status: Status {
|
// TODO: dependency info
|
||||||
configured: false, // TODO
|
Ok(())
|
||||||
main: MainStatus::Stopped, // TODO
|
|
||||||
dependency_config_errors: Default::default(), // TODO
|
|
||||||
},
|
|
||||||
interface_addresses: Default::default(), // TODO
|
|
||||||
marketplace_url: None, // TODO
|
|
||||||
manifest: manifest.clone(),
|
|
||||||
last_backup: None, // TODO
|
|
||||||
hosts: Default::default(), // TODO
|
|
||||||
store_exposed_dependents: Default::default(), // TODO
|
|
||||||
store_exposed_ui: Default::default(), // TODO
|
|
||||||
},
|
|
||||||
manifest,
|
|
||||||
static_files,
|
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
Ok(service)
|
Ok(service)
|
||||||
@@ -466,11 +455,7 @@ impl Actor for ServiceActor {
|
|||||||
seed.ctx
|
seed.ctx
|
||||||
.db
|
.db
|
||||||
.mutate(|d| {
|
.mutate(|d| {
|
||||||
if let Some(i) = d
|
if let Some(i) = d.as_public_mut().as_package_data_mut().as_idx_mut(&id)
|
||||||
.as_public_mut()
|
|
||||||
.as_package_data_mut()
|
|
||||||
.as_idx_mut(&id)
|
|
||||||
.and_then(|p| p.as_installed_mut())
|
|
||||||
{
|
{
|
||||||
i.as_status_mut().as_main_mut().ser(&main_status)?;
|
i.as_status_mut().as_main_mut().ser(&main_status)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::os::unix::process::CommandExt;
|
use std::os::unix::process::CommandExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -6,14 +7,16 @@ use std::sync::{Arc, Weak};
|
|||||||
|
|
||||||
use clap::builder::ValueParserFactory;
|
use clap::builder::ValueParserFactory;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use imbl::OrdMap;
|
||||||
use imbl_value::{json, InternedString};
|
use imbl_value::{json, InternedString};
|
||||||
use models::{ActionId, HealthCheckId, ImageId, PackageId};
|
use models::{ActionId, HealthCheckId, ImageId, InvalidId, PackageId};
|
||||||
use patch_db::json_ptr::JsonPointer;
|
use patch_db::json_ptr::JsonPointer;
|
||||||
use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler};
|
use rpc_toolkit::{from_fn, from_fn_async, AnyContext, Context, Empty, HandlerExt, ParentHandler};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::db::model::ExposedUI;
|
use crate::db::model::package::{CurrentDependencies, CurrentDependencyInfo, ExposedUI};
|
||||||
use crate::disk::mount::filesystem::idmapped::IdMapped;
|
use crate::disk::mount::filesystem::idmapped::IdMapped;
|
||||||
use crate::disk::mount::filesystem::loop_dev::LoopDev;
|
use crate::disk::mount::filesystem::loop_dev::LoopDev;
|
||||||
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
|
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
|
||||||
@@ -131,29 +134,250 @@ pub fn service_effect_handler() -> ParentHandler {
|
|||||||
.subcommand("clearBindings", from_fn_async(clear_bindings).no_cli())
|
.subcommand("clearBindings", from_fn_async(clear_bindings).no_cli())
|
||||||
.subcommand("bind", from_fn_async(bind).no_cli())
|
.subcommand("bind", from_fn_async(bind).no_cli())
|
||||||
.subcommand("getHostInfo", from_fn_async(get_host_info).no_cli())
|
.subcommand("getHostInfo", from_fn_async(get_host_info).no_cli())
|
||||||
// TODO @DrBonez when we get the new api for 4.0
|
.subcommand(
|
||||||
// .subcommand("setDependencies",from_fn_async(set_dependencies).no_cli())
|
"setDependencies",
|
||||||
// .subcommand("embassyGetInterface",from_fn_async(embassy_get_interface).no_cli())
|
from_fn_async(set_dependencies)
|
||||||
// .subcommand("mount",from_fn_async(mount).no_cli())
|
.no_display()
|
||||||
// .subcommand("removeAction",from_fn_async(remove_action).no_cli())
|
.with_remote_cli::<ContainerCliContext>(),
|
||||||
// .subcommand("removeAddress",from_fn_async(remove_address).no_cli())
|
)
|
||||||
// .subcommand("exportAction",from_fn_async(export_action).no_cli())
|
.subcommand("getSystemSmtp", from_fn_async(get_system_smtp).no_cli())
|
||||||
// .subcommand("clearServiceInterfaces",from_fn_async(clear_network_interfaces).no_cli())
|
.subcommand("getContainerIp", from_fn_async(get_container_ip).no_cli())
|
||||||
// .subcommand("exportServiceInterface",from_fn_async(export_network_interface).no_cli())
|
.subcommand(
|
||||||
// .subcommand("getHostnames",from_fn_async(get_hostnames).no_cli())
|
"getServicePortForward",
|
||||||
// .subcommand("getInterface",from_fn_async(get_interface).no_cli())
|
from_fn_async(get_service_port_forward).no_cli(),
|
||||||
// .subcommand("listInterface",from_fn_async(list_interface).no_cli())
|
)
|
||||||
// .subcommand("getIPHostname",from_fn_async(get_ip_hostname).no_cli())
|
.subcommand(
|
||||||
// .subcommand("getContainerIp",from_fn_async(get_container_ip).no_cli())
|
"clearServiceInterfaces",
|
||||||
// .subcommand("getLocalHostname",from_fn_async(get_local_hostname).no_cli())
|
from_fn_async(clear_network_interfaces).no_cli(),
|
||||||
// .subcommand("getPrimaryUrl",from_fn_async(get_primary_url).no_cli())
|
)
|
||||||
// .subcommand("getServicePortForward",from_fn_async(get_service_port_forward).no_cli())
|
.subcommand(
|
||||||
// .subcommand("getServiceTorHostname",from_fn_async(get_service_tor_hostname).no_cli())
|
"exportServiceInterface",
|
||||||
// .subcommand("getSystemSmtp",from_fn_async(get_system_smtp).no_cli())
|
from_fn_async(export_service_interface).no_cli(),
|
||||||
// .subcommand("reverseProxy",from_fn_async(reverse_proxy).no_cli())
|
)
|
||||||
|
.subcommand("getPrimaryUrl", from_fn_async(get_primary_url).no_cli())
|
||||||
|
.subcommand(
|
||||||
|
"listServiceInterfaces",
|
||||||
|
from_fn_async(list_service_interfaces).no_cli(),
|
||||||
|
)
|
||||||
|
.subcommand("removeAddress", from_fn_async(remove_address).no_cli())
|
||||||
|
.subcommand("exportAction", from_fn_async(export_action).no_cli())
|
||||||
|
.subcommand("removeAction", from_fn_async(remove_action).no_cli())
|
||||||
|
.subcommand("reverseProxy", from_fn_async(reverse_proxy).no_cli())
|
||||||
|
.subcommand("mount", from_fn_async(mount).no_cli())
|
||||||
// TODO Callbacks
|
// TODO Callbacks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct GetSystemSmtpParams {
|
||||||
|
callback: Callback,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct GetServicePortForwardParams {
|
||||||
|
#[ts(type = "string | null")]
|
||||||
|
package_id: Option<PackageId>,
|
||||||
|
internal_port: u32,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct BindOptionsSecure {
|
||||||
|
ssl: bool,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct BindOptions {
|
||||||
|
scheme: Option<String>,
|
||||||
|
preferred_external_port: u32,
|
||||||
|
add_ssl: Option<AddSslOptions>,
|
||||||
|
secure: Option<BindOptionsSecure>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct AddressInfo {
|
||||||
|
username: Option<String>,
|
||||||
|
host_id: String,
|
||||||
|
bind_options: BindOptions,
|
||||||
|
suffix: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
enum ServiceInterfaceType {
|
||||||
|
Ui,
|
||||||
|
P2p,
|
||||||
|
Api,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ExportServiceInterfaceParams {
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
has_primary: bool,
|
||||||
|
disabled: bool,
|
||||||
|
masked: bool,
|
||||||
|
address_info: AddressInfo,
|
||||||
|
r#type: ServiceInterfaceType,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct GetPrimaryUrlParams {
|
||||||
|
#[ts(type = "string | null")]
|
||||||
|
package_id: Option<PackageId>,
|
||||||
|
service_interface_id: String,
|
||||||
|
callback: Callback,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ListServiceInterfacesParams {
|
||||||
|
#[ts(type = "string | null")]
|
||||||
|
package_id: Option<PackageId>,
|
||||||
|
callback: Callback,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct RemoveAddressParams {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
enum AllowedStatuses {
|
||||||
|
OnlyRunning,
|
||||||
|
OnlyStopped,
|
||||||
|
Any,
|
||||||
|
Disabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ExportActionParams {
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
id: String,
|
||||||
|
#[ts(type = "{[key: string]: any}")]
|
||||||
|
input: Value,
|
||||||
|
allowed_statuses: AllowedStatuses,
|
||||||
|
group: Option<String>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct RemoveActionParams {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ReverseProxyBind {
|
||||||
|
ip: Option<String>,
|
||||||
|
port: u32,
|
||||||
|
ssl: bool,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ReverseProxyDestination {
|
||||||
|
ip: Option<String>,
|
||||||
|
port: u32,
|
||||||
|
ssl: bool,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ReverseProxyHttp {
|
||||||
|
#[ts(type = "null | {[key: string]: string}")]
|
||||||
|
headers: Option<OrdMap<String, String>>,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ReverseProxyParams {
|
||||||
|
bind: ReverseProxyBind,
|
||||||
|
dst: ReverseProxyDestination,
|
||||||
|
http: ReverseProxyHttp,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct MountTarget {
|
||||||
|
#[ts(type = "string")]
|
||||||
|
package_id: PackageId,
|
||||||
|
volume_id: String,
|
||||||
|
path: String,
|
||||||
|
readonly: bool,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct MountParams {
|
||||||
|
location: String,
|
||||||
|
target: MountTarget,
|
||||||
|
}
|
||||||
|
async fn get_system_smtp(
|
||||||
|
context: EffectContext,
|
||||||
|
data: GetSystemSmtpParams,
|
||||||
|
) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn get_container_ip(context: EffectContext, _: Empty) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn get_service_port_forward(
|
||||||
|
context: EffectContext,
|
||||||
|
data: GetServicePortForwardParams,
|
||||||
|
) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn clear_network_interfaces(context: EffectContext, _: Empty) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn export_service_interface(
|
||||||
|
context: EffectContext,
|
||||||
|
data: ExportServiceInterfaceParams,
|
||||||
|
) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn get_primary_url(
|
||||||
|
context: EffectContext,
|
||||||
|
data: GetPrimaryUrlParams,
|
||||||
|
) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn list_service_interfaces(
|
||||||
|
context: EffectContext,
|
||||||
|
data: ListServiceInterfacesParams,
|
||||||
|
) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn remove_address(context: EffectContext, data: RemoveAddressParams) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn export_action(context: EffectContext, data: ExportActionParams) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn remove_action(context: EffectContext, data: RemoveActionParams) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn reverse_proxy(context: EffectContext, data: ReverseProxyParams) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn mount(context: EffectContext, data: MountParams) -> Result<Value, Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
struct Callback(#[ts(type = "() => void")] i64);
|
struct Callback(#[ts(type = "() => void")] i64);
|
||||||
@@ -170,7 +394,8 @@ enum GetHostInfoParamsKind {
|
|||||||
struct GetHostInfoParams {
|
struct GetHostInfoParams {
|
||||||
kind: Option<GetHostInfoParamsKind>,
|
kind: Option<GetHostInfoParamsKind>,
|
||||||
service_interface_id: String,
|
service_interface_id: String,
|
||||||
package_id: Option<String>,
|
#[ts(type = "string | null")]
|
||||||
|
package_id: Option<PackageId>,
|
||||||
callback: Callback,
|
callback: Callback,
|
||||||
}
|
}
|
||||||
async fn get_host_info(
|
async fn get_host_info(
|
||||||
@@ -211,8 +436,7 @@ struct BindParams {
|
|||||||
scheme: String,
|
scheme: String,
|
||||||
preferred_external_port: u32,
|
preferred_external_port: u32,
|
||||||
add_ssl: Option<AddSslOptions>,
|
add_ssl: Option<AddSslOptions>,
|
||||||
secure: bool,
|
secure: Option<BindOptionsSecure>,
|
||||||
ssl: bool,
|
|
||||||
}
|
}
|
||||||
async fn bind(_: AnyContext, BindParams { .. }: BindParams) -> Result<Value, Error> {
|
async fn bind(_: AnyContext, BindParams { .. }: BindParams) -> Result<Value, Error> {
|
||||||
todo!()
|
todo!()
|
||||||
@@ -222,6 +446,7 @@ async fn bind(_: AnyContext, BindParams { .. }: BindParams) -> Result<Value, Err
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
struct GetServiceInterfaceParams {
|
struct GetServiceInterfaceParams {
|
||||||
|
#[ts(type = "string | null")]
|
||||||
package_id: Option<PackageId>,
|
package_id: Option<PackageId>,
|
||||||
service_interface_id: String,
|
service_interface_id: String,
|
||||||
callback: Callback,
|
callback: Callback,
|
||||||
@@ -375,6 +600,7 @@ async fn get_ssl_key(
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
struct GetStoreParams {
|
struct GetStoreParams {
|
||||||
|
#[ts(type = "string | null")]
|
||||||
package_id: Option<PackageId>,
|
package_id: Option<PackageId>,
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
path: JsonPointer,
|
path: JsonPointer,
|
||||||
@@ -457,8 +683,6 @@ async fn expose_for_dependents(
|
|||||||
.as_package_data_mut()
|
.as_package_data_mut()
|
||||||
.as_idx_mut(&package_id)
|
.as_idx_mut(&package_id)
|
||||||
.or_not_found(&package_id)?
|
.or_not_found(&package_id)?
|
||||||
.as_installed_mut()
|
|
||||||
.or_not_found(&package_id)?
|
|
||||||
.as_store_exposed_dependents_mut()
|
.as_store_exposed_dependents_mut()
|
||||||
.ser(&paths)
|
.ser(&paths)
|
||||||
})
|
})
|
||||||
@@ -467,37 +691,45 @@ async fn expose_for_dependents(
|
|||||||
}
|
}
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[serde(tag = "type")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
struct ExposeUiParams {
|
enum ExposeUiParams {
|
||||||
paths: Vec<ExposedUI>,
|
Object {
|
||||||
|
#[ts(type = "{[key: string]: ExposeUiParams}")]
|
||||||
|
value: OrdMap<String, ExposeUiParams>,
|
||||||
|
},
|
||||||
|
String {
|
||||||
|
path: String,
|
||||||
|
description: Option<String>,
|
||||||
|
masked: bool,
|
||||||
|
copyable: Option<bool>,
|
||||||
|
qr: Option<bool>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn expose_ui(
|
async fn expose_ui(context: EffectContext, params: ExposeUiParams) -> Result<(), Error> {
|
||||||
context: EffectContext,
|
todo!()
|
||||||
ExposeUiParams { paths }: ExposeUiParams,
|
// let context = context.deref()?;
|
||||||
) -> Result<(), Error> {
|
// let package_id = context.id.clone();
|
||||||
let context = context.deref()?;
|
// context
|
||||||
let package_id = context.id.clone();
|
// .ctx
|
||||||
context
|
// .db
|
||||||
.ctx
|
// .mutate(|db| {
|
||||||
.db
|
// db.as_public_mut()
|
||||||
.mutate(|db| {
|
// .as_package_data_mut()
|
||||||
db.as_public_mut()
|
// .as_idx_mut(&package_id)
|
||||||
.as_package_data_mut()
|
// .or_not_found(&package_id)?
|
||||||
.as_idx_mut(&package_id)
|
// .as_store_exposed_ui_mut()
|
||||||
.or_not_found(&package_id)?
|
// .ser(&paths)
|
||||||
.as_installed_mut()
|
// })
|
||||||
.or_not_found(&package_id)?
|
// .await?;
|
||||||
.as_store_exposed_ui_mut()
|
// Ok(())
|
||||||
.ser(&paths)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct ParamsPackageId {
|
struct ParamsPackageId {
|
||||||
|
#[ts(type = "string")]
|
||||||
package_id: PackageId,
|
package_id: PackageId,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)]
|
||||||
@@ -505,6 +737,7 @@ struct ParamsPackageId {
|
|||||||
#[command(rename_all = "camelCase")]
|
#[command(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
struct ParamsMaybePackageId {
|
struct ParamsMaybePackageId {
|
||||||
|
#[ts(type = "string | null")]
|
||||||
package_id: Option<PackageId>,
|
package_id: Option<PackageId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,7 +756,9 @@ async fn exists(context: EffectContext, params: ParamsPackageId) -> Result<Value
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
struct ExecuteAction {
|
struct ExecuteAction {
|
||||||
|
#[ts(type = "string | null")]
|
||||||
service_id: Option<PackageId>,
|
service_id: Option<PackageId>,
|
||||||
|
#[ts(type = "string")]
|
||||||
action_id: ActionId,
|
action_id: ActionId,
|
||||||
#[ts(type = "any")]
|
#[ts(type = "any")]
|
||||||
input: Value,
|
input: Value,
|
||||||
@@ -560,8 +795,6 @@ async fn get_configured(context: EffectContext, _: Empty) -> Result<Value, Error
|
|||||||
.as_package_data()
|
.as_package_data()
|
||||||
.as_idx(&package_id)
|
.as_idx(&package_id)
|
||||||
.or_not_found(&package_id)?
|
.or_not_found(&package_id)?
|
||||||
.as_installed()
|
|
||||||
.or_not_found(&package_id)?
|
|
||||||
.as_status()
|
.as_status()
|
||||||
.as_configured()
|
.as_configured()
|
||||||
.de()?;
|
.de()?;
|
||||||
@@ -577,25 +810,21 @@ async fn stopped(context: EffectContext, params: ParamsMaybePackageId) -> Result
|
|||||||
.as_package_data()
|
.as_package_data()
|
||||||
.as_idx(&package_id)
|
.as_idx(&package_id)
|
||||||
.or_not_found(&package_id)?
|
.or_not_found(&package_id)?
|
||||||
.as_installed()
|
|
||||||
.or_not_found(&package_id)?
|
|
||||||
.as_status()
|
.as_status()
|
||||||
.as_main()
|
.as_main()
|
||||||
.de()?;
|
.de()?;
|
||||||
Ok(json!(matches!(package, MainStatus::Stopped)))
|
Ok(json!(matches!(package, MainStatus::Stopped)))
|
||||||
}
|
}
|
||||||
async fn running(context: EffectContext, params: ParamsMaybePackageId) -> Result<Value, Error> {
|
async fn running(context: EffectContext, params: ParamsPackageId) -> Result<Value, Error> {
|
||||||
dbg!("Starting the running {params:?}");
|
dbg!("Starting the running {params:?}");
|
||||||
let context = context.deref()?;
|
let context = context.deref()?;
|
||||||
let peeked = context.ctx.db.peek().await;
|
let peeked = context.ctx.db.peek().await;
|
||||||
let package_id = params.package_id.unwrap_or_else(|| context.id.clone());
|
let package_id = params.package_id;
|
||||||
let package = peeked
|
let package = peeked
|
||||||
.as_public()
|
.as_public()
|
||||||
.as_package_data()
|
.as_package_data()
|
||||||
.as_idx(&package_id)
|
.as_idx(&package_id)
|
||||||
.or_not_found(&package_id)?
|
.or_not_found(&package_id)?
|
||||||
.as_installed()
|
|
||||||
.or_not_found(&package_id)?
|
|
||||||
.as_status()
|
.as_status()
|
||||||
.as_main()
|
.as_main()
|
||||||
.de()?;
|
.de()?;
|
||||||
@@ -646,8 +875,6 @@ async fn set_configured(context: EffectContext, params: SetConfigured) -> Result
|
|||||||
.as_package_data_mut()
|
.as_package_data_mut()
|
||||||
.as_idx_mut(package_id)
|
.as_idx_mut(package_id)
|
||||||
.or_not_found(package_id)?
|
.or_not_found(package_id)?
|
||||||
.as_installed_mut()
|
|
||||||
.or_not_found(package_id)?
|
|
||||||
.as_status_mut()
|
.as_status_mut()
|
||||||
.as_configured_mut()
|
.as_configured_mut()
|
||||||
.ser(¶ms.configured)
|
.ser(¶ms.configured)
|
||||||
@@ -701,6 +928,7 @@ async fn set_main_status(context: EffectContext, params: SetMainStatus) -> Resul
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
struct SetHealth {
|
struct SetHealth {
|
||||||
|
#[ts(type = "string")]
|
||||||
name: HealthCheckId,
|
name: HealthCheckId,
|
||||||
status: HealthCheckString,
|
status: HealthCheckString,
|
||||||
message: Option<String>,
|
message: Option<String>,
|
||||||
@@ -726,8 +954,6 @@ async fn set_health(
|
|||||||
.as_package_data()
|
.as_package_data()
|
||||||
.as_idx(package_id)
|
.as_idx(package_id)
|
||||||
.or_not_found(package_id)?
|
.or_not_found(package_id)?
|
||||||
.as_installed()
|
|
||||||
.or_not_found(package_id)?
|
|
||||||
.as_status()
|
.as_status()
|
||||||
.as_main()
|
.as_main()
|
||||||
.de()?;
|
.de()?;
|
||||||
@@ -757,8 +983,6 @@ async fn set_health(
|
|||||||
.as_package_data_mut()
|
.as_package_data_mut()
|
||||||
.as_idx_mut(package_id)
|
.as_idx_mut(package_id)
|
||||||
.or_not_found(package_id)?
|
.or_not_found(package_id)?
|
||||||
.as_installed_mut()
|
|
||||||
.or_not_found(package_id)?
|
|
||||||
.as_status_mut()
|
.as_status_mut()
|
||||||
.as_main_mut()
|
.as_main_mut()
|
||||||
.ser(&main)
|
.ser(&main)
|
||||||
@@ -771,7 +995,6 @@ async fn set_health(
|
|||||||
#[command(rename_all = "camelCase")]
|
#[command(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct DestroyOverlayedImageParams {
|
pub struct DestroyOverlayedImageParams {
|
||||||
image_id: ImageId,
|
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
guid: InternedString,
|
guid: InternedString,
|
||||||
}
|
}
|
||||||
@@ -779,7 +1002,7 @@ pub struct DestroyOverlayedImageParams {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn destroy_overlayed_image(
|
pub async fn destroy_overlayed_image(
|
||||||
ctx: EffectContext,
|
ctx: EffectContext,
|
||||||
DestroyOverlayedImageParams { image_id, guid }: DestroyOverlayedImageParams,
|
DestroyOverlayedImageParams { guid }: DestroyOverlayedImageParams,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let ctx = ctx.deref()?;
|
let ctx = ctx.deref()?;
|
||||||
if ctx
|
if ctx
|
||||||
@@ -799,6 +1022,7 @@ pub async fn destroy_overlayed_image(
|
|||||||
#[command(rename_all = "camelCase")]
|
#[command(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct CreateOverlayedImageParams {
|
pub struct CreateOverlayedImageParams {
|
||||||
|
#[ts(type = "string")]
|
||||||
image_id: ImageId,
|
image_id: ImageId,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,3 +1088,127 @@ pub async fn create_overlayed_image(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export)]
|
||||||
|
enum DependencyKind {
|
||||||
|
Exists,
|
||||||
|
Running,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export)]
|
||||||
|
struct DependencyRequirement {
|
||||||
|
#[ts(type = "string")]
|
||||||
|
id: PackageId,
|
||||||
|
kind: DependencyKind,
|
||||||
|
#[serde(default)]
|
||||||
|
#[ts(type = "string[]")]
|
||||||
|
health_checks: BTreeSet<HealthCheckId>,
|
||||||
|
}
|
||||||
|
// filebrowser:exists,bitcoind:running:foo+bar+baz
|
||||||
|
impl FromStr for DependencyRequirement {
|
||||||
|
type Err = Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s.split_once(':') {
|
||||||
|
Some((id, "e")) | Some((id, "exists")) => Ok(Self {
|
||||||
|
id: id.parse()?,
|
||||||
|
kind: DependencyKind::Exists,
|
||||||
|
health_checks: BTreeSet::new(),
|
||||||
|
}),
|
||||||
|
Some((id, rest)) => {
|
||||||
|
let health_checks = match rest.split_once(":") {
|
||||||
|
Some(("r", rest)) | Some(("running", rest)) => rest
|
||||||
|
.split('+')
|
||||||
|
.map(|id| id.parse().map_err(Error::from))
|
||||||
|
.collect(),
|
||||||
|
Some((kind, _)) => Err(Error::new(
|
||||||
|
eyre!("unknown dependency kind {kind}"),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
)),
|
||||||
|
None => match rest {
|
||||||
|
"r" | "running" => Ok(BTreeSet::new()),
|
||||||
|
kind => Err(Error::new(
|
||||||
|
eyre!("unknown dependency kind {kind}"),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
}?;
|
||||||
|
Ok(Self {
|
||||||
|
id: id.parse()?,
|
||||||
|
kind: DependencyKind::Running,
|
||||||
|
health_checks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
None => Ok(Self {
|
||||||
|
id: s.parse()?,
|
||||||
|
kind: DependencyKind::Running,
|
||||||
|
health_checks: BTreeSet::new(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ValueParserFactory for DependencyRequirement {
|
||||||
|
type Parser = FromStrParser<Self>;
|
||||||
|
fn value_parser() -> Self::Parser {
|
||||||
|
FromStrParser::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[command(rename_all = "camelCase")]
|
||||||
|
#[ts(export)]
|
||||||
|
struct SetDependenciesParams {
|
||||||
|
dependencies: Vec<DependencyRequirement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_dependencies(
|
||||||
|
ctx: EffectContext,
|
||||||
|
SetDependenciesParams { dependencies }: SetDependenciesParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let ctx = ctx.deref()?;
|
||||||
|
let id = &ctx.id;
|
||||||
|
ctx.ctx
|
||||||
|
.db
|
||||||
|
.mutate(|db| {
|
||||||
|
let dependencies = CurrentDependencies(
|
||||||
|
dependencies
|
||||||
|
.into_iter()
|
||||||
|
.map(
|
||||||
|
|DependencyRequirement {
|
||||||
|
id,
|
||||||
|
kind,
|
||||||
|
health_checks,
|
||||||
|
}| {
|
||||||
|
(
|
||||||
|
id,
|
||||||
|
match kind {
|
||||||
|
DependencyKind::Exists => CurrentDependencyInfo::Exists,
|
||||||
|
DependencyKind::Running => {
|
||||||
|
CurrentDependencyInfo::Running { health_checks }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
for (dep, entry) in db.as_public_mut().as_package_data_mut().as_entries_mut()? {
|
||||||
|
if let Some(info) = dependencies.0.get(&dep) {
|
||||||
|
entry.as_current_dependents_mut().insert(id, info)?;
|
||||||
|
} else {
|
||||||
|
entry.as_current_dependents_mut().remove(id)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.as_public_mut()
|
||||||
|
.as_package_data_mut()
|
||||||
|
.as_idx_mut(id)
|
||||||
|
.or_not_found(id)?
|
||||||
|
.as_current_dependencies_mut()
|
||||||
|
.ser(&dependencies)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ use tokio::sync::{Mutex, OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock};
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::db::model::{
|
use crate::db::model::package::{
|
||||||
PackageDataEntry, PackageDataEntryInstalled, PackageDataEntryInstalling,
|
InstallingInfo, InstallingState, PackageDataEntry, PackageState, UpdatingState,
|
||||||
PackageDataEntryRestoring, PackageDataEntryUpdating, StaticFiles,
|
|
||||||
};
|
};
|
||||||
use crate::disk::mount::guard::GenericMountGuard;
|
use crate::disk::mount::guard::GenericMountGuard;
|
||||||
use crate::install::PKG_ARCHIVE_DIR;
|
use crate::install::PKG_ARCHIVE_DIR;
|
||||||
@@ -27,6 +26,7 @@ use crate::s9pk::manifest::PackageId;
|
|||||||
use crate::s9pk::merkle_archive::source::FileSource;
|
use crate::s9pk::merkle_archive::source::FileSource;
|
||||||
use crate::s9pk::S9pk;
|
use crate::s9pk::S9pk;
|
||||||
use crate::service::{LoadDisposition, Service};
|
use crate::service::{LoadDisposition, Service};
|
||||||
|
use crate::status::{MainStatus, Status};
|
||||||
|
|
||||||
pub type DownloadInstallFuture = BoxFuture<'static, Result<InstallFuture, Error>>;
|
pub type DownloadInstallFuture = BoxFuture<'static, Result<InstallFuture, Error>>;
|
||||||
pub type InstallFuture = BoxFuture<'static, Result<(), Error>>;
|
pub type InstallFuture = BoxFuture<'static, Result<(), Error>>;
|
||||||
@@ -95,9 +95,10 @@ impl ServiceMap {
|
|||||||
mut s9pk: S9pk<S>,
|
mut s9pk: S9pk<S>,
|
||||||
recovery_source: Option<impl GenericMountGuard>,
|
recovery_source: Option<impl GenericMountGuard>,
|
||||||
) -> Result<DownloadInstallFuture, Error> {
|
) -> Result<DownloadInstallFuture, Error> {
|
||||||
let manifest = Arc::new(s9pk.as_manifest().clone());
|
let manifest = s9pk.as_manifest().clone();
|
||||||
let id = manifest.id.clone();
|
let id = manifest.id.clone();
|
||||||
let icon = s9pk.icon_data_url().await?;
|
let icon = s9pk.icon_data_url().await?;
|
||||||
|
let developer_key = s9pk.as_archive().signer();
|
||||||
let mut service = self.get_mut(&id).await;
|
let mut service = self.get_mut(&id).await;
|
||||||
|
|
||||||
let op_name = if recovery_source.is_none() {
|
let op_name = if recovery_source.is_none() {
|
||||||
@@ -135,49 +136,51 @@ impl ServiceMap {
|
|||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
let install_progress = progress.snapshot();
|
let install_progress = progress.snapshot();
|
||||||
move |db| {
|
move |db| {
|
||||||
let pde = match db
|
if let Some(pde) = db.as_public_mut().as_package_data_mut().as_idx_mut(&id) {
|
||||||
.as_public()
|
let prev = pde.as_state_info().expect_installed()?.de()?;
|
||||||
.as_package_data()
|
pde.as_state_info_mut()
|
||||||
.as_idx(&id)
|
.ser(&PackageState::Updating(UpdatingState {
|
||||||
.map(|x| x.de())
|
manifest: prev.manifest,
|
||||||
.transpose()?
|
installing_info: InstallingInfo {
|
||||||
{
|
new_manifest: manifest,
|
||||||
Some(PackageDataEntry::Installed(PackageDataEntryInstalled {
|
progress: install_progress,
|
||||||
installed,
|
},
|
||||||
static_files,
|
}))?;
|
||||||
..
|
} else {
|
||||||
})) => PackageDataEntry::Updating(PackageDataEntryUpdating {
|
let installing = InstallingState {
|
||||||
install_progress,
|
installing_info: InstallingInfo {
|
||||||
installed,
|
new_manifest: manifest,
|
||||||
manifest: (*manifest).clone(),
|
progress: install_progress,
|
||||||
static_files,
|
},
|
||||||
}),
|
};
|
||||||
None if restoring => {
|
db.as_public_mut().as_package_data_mut().insert(
|
||||||
PackageDataEntry::Restoring(PackageDataEntryRestoring {
|
&id,
|
||||||
install_progress,
|
&PackageDataEntry {
|
||||||
static_files: StaticFiles::local(
|
state_info: if restoring {
|
||||||
&manifest.id,
|
PackageState::Restoring(installing)
|
||||||
&manifest.version,
|
} else {
|
||||||
icon,
|
PackageState::Installing(installing)
|
||||||
),
|
},
|
||||||
manifest: (*manifest).clone(),
|
status: Status {
|
||||||
})
|
configured: false,
|
||||||
}
|
main: MainStatus::Stopped,
|
||||||
None => PackageDataEntry::Installing(PackageDataEntryInstalling {
|
dependency_config_errors: Default::default(),
|
||||||
install_progress,
|
},
|
||||||
static_files: StaticFiles::local(&manifest.id, &manifest.version, icon),
|
marketplace_url: None,
|
||||||
manifest: (*manifest).clone(),
|
developer_key,
|
||||||
}),
|
icon,
|
||||||
_ => {
|
last_backup: None,
|
||||||
return Err(Error::new(
|
dependency_info: Default::default(),
|
||||||
eyre!("Cannot install over a package in a transient state"),
|
current_dependents: Default::default(), // TODO: initialize
|
||||||
crate::ErrorKind::InvalidRequest,
|
current_dependencies: Default::default(),
|
||||||
))
|
interface_addresses: Default::default(),
|
||||||
}
|
hosts: Default::default(),
|
||||||
|
store_exposed_ui: Default::default(),
|
||||||
|
store_exposed_dependents: Default::default(),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
};
|
};
|
||||||
db.as_public_mut()
|
Ok(())
|
||||||
.as_package_data_mut()
|
|
||||||
.insert(&manifest.id, &pde)
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.await?;
|
.await?;
|
||||||
@@ -200,7 +203,8 @@ impl ServiceMap {
|
|||||||
v.as_public_mut()
|
v.as_public_mut()
|
||||||
.as_package_data_mut()
|
.as_package_data_mut()
|
||||||
.as_idx_mut(&deref_id)
|
.as_idx_mut(&deref_id)
|
||||||
.and_then(|e| e.as_install_progress_mut())
|
.and_then(|e| e.as_state_info_mut().as_installing_info_mut())
|
||||||
|
.map(|i| i.as_progress_mut())
|
||||||
},
|
},
|
||||||
Some(Duration::from_millis(100)),
|
Some(Duration::from_millis(100)),
|
||||||
)));
|
)));
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ impl std::fmt::Display for HealthCheckResult {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export)]
|
||||||
pub enum HealthCheckString {
|
pub enum HealthCheckString {
|
||||||
Passing,
|
Passing,
|
||||||
Disabled,
|
Disabled,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use tokio_stream::StreamExt;
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::db::model::UpdateProgress;
|
use crate::db::model::public::UpdateProgress;
|
||||||
use crate::disk::mount::filesystem::bind::Bind;
|
use crate::disk::mount::filesystem::bind::Bind;
|
||||||
use crate::disk::mount::filesystem::ReadWrite;
|
use crate::disk::mount::filesystem::ReadWrite;
|
||||||
use crate::disk::mount::guard::MountGuard;
|
use crate::disk::mount::guard::MountGuard;
|
||||||
|
|||||||
@@ -19,9 +19,14 @@ import {
|
|||||||
BackupOptions,
|
BackupOptions,
|
||||||
DeepPartial,
|
DeepPartial,
|
||||||
MaybePromise,
|
MaybePromise,
|
||||||
|
ServiceInterfaceId,
|
||||||
|
PackageId,
|
||||||
|
EnsureStorePath,
|
||||||
|
ExtractStore,
|
||||||
|
DaemonReturned,
|
||||||
|
ValidIfNoStupidEscape,
|
||||||
} from "./types"
|
} from "./types"
|
||||||
import * as patterns from "./util/patterns"
|
import * as patterns from "./util/patterns"
|
||||||
import { Utils } from "./util/utils"
|
|
||||||
import { DependencyConfig, Update } from "./dependencyConfig/DependencyConfig"
|
import { DependencyConfig, Update } from "./dependencyConfig/DependencyConfig"
|
||||||
import { BackupSet, Backups } from "./backup/Backups"
|
import { BackupSet, Backups } from "./backup/Backups"
|
||||||
import { smtpConfig } from "./config/configConstants"
|
import { smtpConfig } from "./config/configConstants"
|
||||||
@@ -53,6 +58,19 @@ import {
|
|||||||
} from "./interfaces/setupInterfaces"
|
} from "./interfaces/setupInterfaces"
|
||||||
import { successFailure } from "./trigger/successFailure"
|
import { successFailure } from "./trigger/successFailure"
|
||||||
import { SetupExports } from "./inits/setupExports"
|
import { SetupExports } from "./inits/setupExports"
|
||||||
|
import { HealthReceipt } from "./health/HealthReceipt"
|
||||||
|
import { MultiHost, Scheme, SingleHost, StaticHost } from "./interfaces/Host"
|
||||||
|
import { ServiceInterfaceBuilder } from "./interfaces/ServiceInterfaceBuilder"
|
||||||
|
import { GetSystemSmtp } from "./util/GetSystemSmtp"
|
||||||
|
import nullIfEmpty from "./util/nullIfEmpty"
|
||||||
|
import {
|
||||||
|
GetServiceInterface,
|
||||||
|
getServiceInterface,
|
||||||
|
} from "./util/getServiceInterface"
|
||||||
|
import { getServiceInterfaces } from "./util/getServiceInterfaces"
|
||||||
|
import { getStore } from "./store/getStore"
|
||||||
|
import { CommandOptions, MountOptions, Overlay } from "./util/Overlay"
|
||||||
|
import { splitCommand } from "./util/splitCommand"
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
type AnyNeverCond<T extends any[], Then, Else> =
|
type AnyNeverCond<T extends any[], Then, Else> =
|
||||||
@@ -61,6 +79,17 @@ type AnyNeverCond<T extends any[], Then, Else> =
|
|||||||
T extends [any, ...infer U] ? AnyNeverCond<U,Then, Else> :
|
T extends [any, ...infer U] ? AnyNeverCond<U,Then, Else> :
|
||||||
never
|
never
|
||||||
|
|
||||||
|
export type ServiceInterfaceType = "ui" | "p2p" | "api"
|
||||||
|
export type MainEffects = Effects & { _type: "main" }
|
||||||
|
export type Signals = NodeJS.Signals
|
||||||
|
export const SIGTERM: Signals = "SIGTERM"
|
||||||
|
export const SIGKILL: Signals = "SIGTERM"
|
||||||
|
export const NO_TIMEOUT = -1
|
||||||
|
|
||||||
|
function removeConstType<E>() {
|
||||||
|
return <T>(t: T) => t as T & (E extends MainEffects ? {} : { const: never })
|
||||||
|
}
|
||||||
|
|
||||||
export class StartSdk<Manifest extends SDKManifest, Store> {
|
export class StartSdk<Manifest extends SDKManifest, Store> {
|
||||||
private constructor(readonly manifest: Manifest) {}
|
private constructor(readonly manifest: Manifest) {}
|
||||||
static of() {
|
static of() {
|
||||||
@@ -75,7 +104,78 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
|
|
||||||
build(isReady: AnyNeverCond<[Manifest, Store], "Build not ready", true>) {
|
build(isReady: AnyNeverCond<[Manifest, Store], "Build not ready", true>) {
|
||||||
return {
|
return {
|
||||||
|
serviceInterface: {
|
||||||
|
getOwn: <E extends Effects>(effects: E, id: ServiceInterfaceId) =>
|
||||||
|
removeConstType<E>()(
|
||||||
|
getServiceInterface(effects, {
|
||||||
|
id,
|
||||||
|
packageId: null,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
get: <E extends Effects>(
|
||||||
|
effects: E,
|
||||||
|
opts: { id: ServiceInterfaceId; packageId: PackageId },
|
||||||
|
) => removeConstType<E>()(getServiceInterface(effects, opts)),
|
||||||
|
getAllOwn: <E extends Effects>(effects: E) =>
|
||||||
|
removeConstType<E>()(
|
||||||
|
getServiceInterfaces(effects, {
|
||||||
|
packageId: null,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
getAll: <E extends Effects>(
|
||||||
|
effects: E,
|
||||||
|
opts: { packageId: PackageId },
|
||||||
|
) => removeConstType<E>()(getServiceInterfaces(effects, opts)),
|
||||||
|
},
|
||||||
|
|
||||||
|
store: {
|
||||||
|
get: <E extends Effects, Path extends string = never>(
|
||||||
|
effects: E,
|
||||||
|
packageId: string,
|
||||||
|
path: EnsureStorePath<Store, Path>,
|
||||||
|
) =>
|
||||||
|
removeConstType<E>()(
|
||||||
|
getStore<Store, Path>(effects, path as any, {
|
||||||
|
packageId,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
getOwn: <E extends Effects, Path extends string>(
|
||||||
|
effects: E,
|
||||||
|
path: EnsureStorePath<Store, Path>,
|
||||||
|
) => removeConstType<E>()(getStore<Store, Path>(effects, path as any)),
|
||||||
|
setOwn: <E extends Effects, Path extends string | never>(
|
||||||
|
effects: E,
|
||||||
|
path: EnsureStorePath<Store, Path>,
|
||||||
|
value: ExtractStore<Store, Path>,
|
||||||
|
) => effects.store.set<Store, Path>({ value, path: path as any }),
|
||||||
|
},
|
||||||
|
|
||||||
|
host: {
|
||||||
|
static: (effects: Effects, id: string) =>
|
||||||
|
new StaticHost({ id, effects }),
|
||||||
|
single: (effects: Effects, id: string) =>
|
||||||
|
new SingleHost({ id, effects }),
|
||||||
|
multi: (effects: Effects, id: string) => new MultiHost({ id, effects }),
|
||||||
|
},
|
||||||
|
nullIfEmpty,
|
||||||
|
|
||||||
configConstants: { smtpConfig },
|
configConstants: { smtpConfig },
|
||||||
|
createInterface: (
|
||||||
|
effects: Effects,
|
||||||
|
options: {
|
||||||
|
name: string
|
||||||
|
id: string
|
||||||
|
description: string
|
||||||
|
hasPrimary: boolean
|
||||||
|
disabled: boolean
|
||||||
|
type: ServiceInterfaceType
|
||||||
|
username: null | string
|
||||||
|
path: string
|
||||||
|
search: Record<string, string>
|
||||||
|
schemeOverride: { ssl: Scheme; noSsl: Scheme } | null
|
||||||
|
masked: boolean
|
||||||
|
},
|
||||||
|
) => new ServiceInterfaceBuilder({ ...options, effects }),
|
||||||
createAction: <
|
createAction: <
|
||||||
ConfigType extends
|
ConfigType extends
|
||||||
| Record<string, any>
|
| Record<string, any>
|
||||||
@@ -88,13 +188,34 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
},
|
},
|
||||||
fn: (options: {
|
fn: (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
input: Type
|
input: Type
|
||||||
}) => Promise<ActionResult>,
|
}) => Promise<ActionResult>,
|
||||||
) => {
|
) => {
|
||||||
const { input, ...rest } = metaData
|
const { input, ...rest } = metaData
|
||||||
return createAction<Manifest, Store, ConfigType, Type>(rest, fn, input)
|
return createAction<Manifest, Store, ConfigType, Type>(rest, fn, input)
|
||||||
},
|
},
|
||||||
|
getSystemSmtp: <E extends Effects>(effects: E) =>
|
||||||
|
removeConstType<E>()(new GetSystemSmtp(effects)),
|
||||||
|
runCommand: async <A extends string>(
|
||||||
|
effects: Effects,
|
||||||
|
imageId: Manifest["images"][number],
|
||||||
|
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
||||||
|
options: CommandOptions & {
|
||||||
|
mounts?: { path: string; options: MountOptions }[]
|
||||||
|
},
|
||||||
|
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> => {
|
||||||
|
const commands = splitCommand(command)
|
||||||
|
const overlay = await Overlay.of(effects, imageId)
|
||||||
|
try {
|
||||||
|
for (let mount of options.mounts || []) {
|
||||||
|
await overlay.mount(mount.options, mount.path)
|
||||||
|
}
|
||||||
|
return await overlay.exec(commands)
|
||||||
|
} finally {
|
||||||
|
await overlay.destroy()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
createDynamicAction: <
|
createDynamicAction: <
|
||||||
ConfigType extends
|
ConfigType extends
|
||||||
| Record<string, any>
|
| Record<string, any>
|
||||||
@@ -104,11 +225,9 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
>(
|
>(
|
||||||
metaData: (options: {
|
metaData: (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => MaybePromise<Omit<ActionMetadata, "input">>,
|
}) => MaybePromise<Omit<ActionMetadata, "input">>,
|
||||||
fn: (options: {
|
fn: (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
input: Type
|
input: Type
|
||||||
}) => Promise<ActionResult>,
|
}) => Promise<ActionResult>,
|
||||||
input: Config<Type, Store> | Config<Type, never>,
|
input: Config<Type, Store> | Config<Type, never>,
|
||||||
@@ -193,9 +312,8 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
) => setupInterfaces(config, fn),
|
) => setupInterfaces(config, fn),
|
||||||
setupMain: (
|
setupMain: (
|
||||||
fn: (o: {
|
fn: (o: {
|
||||||
effects: Effects
|
effects: MainEffects
|
||||||
started(onTerm: () => PromiseLike<void>): PromiseLike<void>
|
started(onTerm: () => PromiseLike<void>): PromiseLike<void>
|
||||||
utils: Utils<Manifest, Store, {}>
|
|
||||||
}) => Promise<Daemons<Manifest, any>>,
|
}) => Promise<Daemons<Manifest, any>>,
|
||||||
) => setupMain<Manifest, Store>(fn),
|
) => setupMain<Manifest, Store>(fn),
|
||||||
setupMigrations: <
|
setupMigrations: <
|
||||||
@@ -232,7 +350,15 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
spec: Spec,
|
spec: Spec,
|
||||||
) => Config.of<Spec, Store>(spec),
|
) => Config.of<Spec, Store>(spec),
|
||||||
},
|
},
|
||||||
Daemons: { of: Daemons.of },
|
Daemons: {
|
||||||
|
of(config: {
|
||||||
|
effects: Effects
|
||||||
|
started: (onTerm: () => PromiseLike<void>) => PromiseLike<void>
|
||||||
|
healthReceipts: HealthReceipt[]
|
||||||
|
}) {
|
||||||
|
return Daemons.of<Manifest>(config)
|
||||||
|
},
|
||||||
|
},
|
||||||
DependencyConfig: {
|
DependencyConfig: {
|
||||||
of<
|
of<
|
||||||
LocalConfig extends Record<string, any>,
|
LocalConfig extends Record<string, any>,
|
||||||
@@ -248,7 +374,6 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
dependencyConfig: (options: {
|
dependencyConfig: (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
localConfig: LocalConfig
|
localConfig: LocalConfig
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<void | DeepPartial<RemoteConfig>>
|
}) => Promise<void | DeepPartial<RemoteConfig>>
|
||||||
update?: Update<void | DeepPartial<RemoteConfig>, RemoteConfig>
|
update?: Update<void | DeepPartial<RemoteConfig>, RemoteConfig>
|
||||||
}) {
|
}) {
|
||||||
@@ -332,14 +457,8 @@ export class StartSdk<Manifest extends SDKManifest, Store> {
|
|||||||
Migration: {
|
Migration: {
|
||||||
of: <Version extends ManifestVersion>(options: {
|
of: <Version extends ManifestVersion>(options: {
|
||||||
version: Version
|
version: Version
|
||||||
up: (opts: {
|
up: (opts: { effects: Effects }) => Promise<void>
|
||||||
effects: Effects
|
down: (opts: { effects: Effects }) => Promise<void>
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<void>
|
|
||||||
down: (opts: {
|
|
||||||
effects: Effects
|
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<void>
|
|
||||||
}) => Migration.of<Manifest, Store, Version>(options),
|
}) => Migration.of<Manifest, Store, Version>(options),
|
||||||
},
|
},
|
||||||
Value: {
|
Value: {
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import { Config, ExtractConfigType } from "../config/builder/config"
|
import { Config, ExtractConfigType } from "../config/builder/config"
|
||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
import { ActionMetadata, ActionResult, Effects, ExportedAction } from "../types"
|
import { ActionMetadata, ActionResult, Effects, ExportedAction } from "../types"
|
||||||
import { createUtils } from "../util"
|
|
||||||
import { Utils } from "../util/utils"
|
|
||||||
|
|
||||||
export type MaybeFn<Manifest extends SDKManifest, Store, Value> =
|
export type MaybeFn<Manifest extends SDKManifest, Store, Value> =
|
||||||
| Value
|
| Value
|
||||||
| ((options: {
|
| ((options: { effects: Effects }) => Promise<Value> | Value)
|
||||||
effects: Effects
|
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<Value> | Value)
|
|
||||||
export class CreatedAction<
|
export class CreatedAction<
|
||||||
Manifest extends SDKManifest,
|
Manifest extends SDKManifest,
|
||||||
Store,
|
Store,
|
||||||
@@ -27,7 +22,6 @@ export class CreatedAction<
|
|||||||
>,
|
>,
|
||||||
readonly fn: (options: {
|
readonly fn: (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
input: Type
|
input: Type
|
||||||
}) => Promise<ActionResult>,
|
}) => Promise<ActionResult>,
|
||||||
readonly input: Config<Type, Store>,
|
readonly input: Config<Type, Store>,
|
||||||
@@ -44,11 +38,7 @@ export class CreatedAction<
|
|||||||
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
Type extends Record<string, any> = ExtractConfigType<ConfigType>,
|
||||||
>(
|
>(
|
||||||
metaData: MaybeFn<Manifest, Store, Omit<ActionMetadata, "input">>,
|
metaData: MaybeFn<Manifest, Store, Omit<ActionMetadata, "input">>,
|
||||||
fn: (options: {
|
fn: (options: { effects: Effects; input: Type }) => Promise<ActionResult>,
|
||||||
effects: Effects
|
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
input: Type
|
|
||||||
}) => Promise<ActionResult>,
|
|
||||||
inputConfig: Config<Type, Store> | Config<Type, never>,
|
inputConfig: Config<Type, Store> | Config<Type, never>,
|
||||||
) {
|
) {
|
||||||
return new CreatedAction<Manifest, Store, ConfigType, Type>(
|
return new CreatedAction<Manifest, Store, ConfigType, Type>(
|
||||||
@@ -61,7 +51,6 @@ export class CreatedAction<
|
|||||||
exportedAction: ExportedAction = ({ effects, input }) => {
|
exportedAction: ExportedAction = ({ effects, input }) => {
|
||||||
return this.fn({
|
return this.fn({
|
||||||
effects,
|
effects,
|
||||||
utils: createUtils(effects),
|
|
||||||
input: this.validator.unsafeCast(input),
|
input: this.validator.unsafeCast(input),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -69,21 +58,17 @@ export class CreatedAction<
|
|||||||
run = async ({ effects, input }: { effects: Effects; input?: Type }) => {
|
run = async ({ effects, input }: { effects: Effects; input?: Type }) => {
|
||||||
return this.fn({
|
return this.fn({
|
||||||
effects,
|
effects,
|
||||||
utils: createUtils(effects),
|
|
||||||
input: this.validator.unsafeCast(input),
|
input: this.validator.unsafeCast(input),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async metaData(options: { effects: Effects; utils: Utils<Manifest, Store> }) {
|
async metaData(options: { effects: Effects }) {
|
||||||
if (this.myMetaData instanceof Function)
|
if (this.myMetaData instanceof Function)
|
||||||
return await this.myMetaData(options)
|
return await this.myMetaData(options)
|
||||||
return this.myMetaData
|
return this.myMetaData
|
||||||
}
|
}
|
||||||
|
|
||||||
async ActionMetadata(options: {
|
async ActionMetadata(options: { effects: Effects }): Promise<ActionMetadata> {
|
||||||
effects: Effects
|
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}): Promise<ActionMetadata> {
|
|
||||||
return {
|
return {
|
||||||
...(await this.metaData(options)),
|
...(await this.metaData(options)),
|
||||||
input: await this.input.build(options),
|
input: await this.input.build(options),
|
||||||
@@ -93,7 +78,6 @@ export class CreatedAction<
|
|||||||
async getConfig({ effects }: { effects: Effects }) {
|
async getConfig({ effects }: { effects: Effects }) {
|
||||||
return this.input.build({
|
return this.input.build({
|
||||||
effects,
|
effects,
|
||||||
utils: createUtils(effects) as any,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
import { Effects, ExpectedExports } from "../types"
|
import { Effects, ExpectedExports } from "../types"
|
||||||
import { createUtils } from "../util"
|
|
||||||
import { once } from "../util/once"
|
import { once } from "../util/once"
|
||||||
import { Utils } from "../util/utils"
|
|
||||||
import { CreatedAction } from "./createAction"
|
import { CreatedAction } from "./createAction"
|
||||||
|
|
||||||
export function setupActions<Manifest extends SDKManifest, Store>(
|
export function setupActions<Manifest extends SDKManifest, Store>(
|
||||||
...createdActions: CreatedAction<Manifest, Store, any>[]
|
...createdActions: CreatedAction<Manifest, Store, any>[]
|
||||||
) {
|
) {
|
||||||
const myActions = async (options: {
|
const myActions = async (options: { effects: Effects }) => {
|
||||||
effects: Effects
|
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => {
|
|
||||||
const actions: Record<string, CreatedAction<Manifest, Store, any>> = {}
|
const actions: Record<string, CreatedAction<Manifest, Store, any>> = {}
|
||||||
for (const action of createdActions) {
|
for (const action of createdActions) {
|
||||||
const actionMetadata = await action.metaData(options)
|
const actionMetadata = await action.metaData(options)
|
||||||
@@ -24,17 +19,11 @@ export function setupActions<Manifest extends SDKManifest, Store>(
|
|||||||
actionsMetadata: ExpectedExports.actionsMetadata
|
actionsMetadata: ExpectedExports.actionsMetadata
|
||||||
} = {
|
} = {
|
||||||
actions(options: { effects: Effects }) {
|
actions(options: { effects: Effects }) {
|
||||||
const utils = createUtils<Manifest, Store>(options.effects)
|
return myActions(options)
|
||||||
|
|
||||||
return myActions({
|
|
||||||
...options,
|
|
||||||
utils,
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
async actionsMetadata({ effects }: { effects: Effects }) {
|
async actionsMetadata({ effects }: { effects: Effects }) {
|
||||||
const utils = createUtils<Manifest, Store>(effects)
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
createdActions.map((x) => x.ActionMetadata({ effects, utils })),
|
createdActions.map((x) => x.ActionMetadata({ effects })),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export class Backups<M extends SDKManifest> {
|
|||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
private options = DEFAULT_OPTIONS,
|
private options = DEFAULT_OPTIONS,
|
||||||
private backupSet = [] as BackupSet<M["volumes"][0]>[],
|
private backupSet = [] as BackupSet<M["volumes"][number]>[],
|
||||||
) {}
|
) {}
|
||||||
static volumes<M extends SDKManifest = never>(
|
static volumes<M extends SDKManifest = never>(
|
||||||
...volumeNames: Array<M["volumes"][0]>
|
...volumeNames: Array<M["volumes"][0]>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ExpectedExports } from "../types"
|
|||||||
import { _ } from "../util"
|
import { _ } from "../util"
|
||||||
|
|
||||||
export type SetupBackupsParams<M extends SDKManifest> = Array<
|
export type SetupBackupsParams<M extends SDKManifest> = Array<
|
||||||
M["volumes"][0] | Backups<M>
|
M["volumes"][number] | Backups<M>
|
||||||
>
|
>
|
||||||
|
|
||||||
export function setupBackups<M extends SDKManifest>(
|
export function setupBackups<M extends SDKManifest>(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ValueSpec } from "../configTypes"
|
import { ValueSpec } from "../configTypes"
|
||||||
import { Utils } from "../../util/utils"
|
|
||||||
import { Value } from "./value"
|
import { Value } from "./value"
|
||||||
import { _ } from "../../util"
|
import { _ } from "../../util"
|
||||||
import { Effects } from "../../types"
|
import { Effects } from "../../types"
|
||||||
@@ -7,7 +6,6 @@ import { Parser, object } from "ts-matches"
|
|||||||
|
|
||||||
export type LazyBuildOptions<Store> = {
|
export type LazyBuildOptions<Store> = {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<any, Store>
|
|
||||||
}
|
}
|
||||||
export type LazyBuild<Store, ExpectedOut> = (
|
export type LazyBuild<Store, ExpectedOut> = (
|
||||||
options: LazyBuildOptions<Store>,
|
options: LazyBuildOptions<Store>,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { SmtpValue } from "../types"
|
import { SmtpValue } from "../types"
|
||||||
|
import { GetSystemSmtp } from "../util/GetSystemSmtp"
|
||||||
import { email } from "../util/patterns"
|
import { email } from "../util/patterns"
|
||||||
import { Config, ConfigSpecOf } from "./builder/config"
|
import { Config, ConfigSpecOf } from "./builder/config"
|
||||||
import { Value } from "./builder/value"
|
import { Value } from "./builder/value"
|
||||||
@@ -47,8 +48,8 @@ export const customSmtp = Config.of<ConfigSpecOf<SmtpValue>, never>({
|
|||||||
* For service config. Gives users 3 options for SMTP: (1) disabled, (2) use system SMTP settings, (3) use custom SMTP settings
|
* For service config. Gives users 3 options for SMTP: (1) disabled, (2) use system SMTP settings, (3) use custom SMTP settings
|
||||||
*/
|
*/
|
||||||
export const smtpConfig = Value.filteredUnion(
|
export const smtpConfig = Value.filteredUnion(
|
||||||
async ({ effects, utils }) => {
|
async ({ effects }) => {
|
||||||
const smtp = await utils.getSystemSmtp().once()
|
const smtp = await new GetSystemSmtp(effects).once()
|
||||||
return smtp ? [] : ["system"]
|
return smtp ? [] : ["system"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Dependency } from "../types"
|
|||||||
|
|
||||||
export type ConfigDependencies<T extends SDKManifest> = {
|
export type ConfigDependencies<T extends SDKManifest> = {
|
||||||
exists(id: keyof T["dependencies"]): Dependency
|
exists(id: keyof T["dependencies"]): Dependency
|
||||||
running(id: keyof T["dependencies"]): Dependency
|
running(id: keyof T["dependencies"], healthChecks: string[]): Dependency
|
||||||
}
|
}
|
||||||
|
|
||||||
export const configDependenciesSet = <
|
export const configDependenciesSet = <
|
||||||
@@ -16,10 +16,11 @@ export const configDependenciesSet = <
|
|||||||
} as Dependency
|
} as Dependency
|
||||||
},
|
},
|
||||||
|
|
||||||
running(id: keyof T["dependencies"]) {
|
running(id: keyof T["dependencies"], healthChecks: string[]) {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
kind: "running",
|
kind: "running",
|
||||||
|
healthChecks,
|
||||||
} as Dependency
|
} as Dependency
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Effects, ExpectedExports } from "../types"
|
|||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
import * as D from "./configDependencies"
|
import * as D from "./configDependencies"
|
||||||
import { Config, ExtractConfigType } from "./builder/config"
|
import { Config, ExtractConfigType } from "./builder/config"
|
||||||
import { Utils, createUtils } from "../util/utils"
|
|
||||||
import nullIfEmpty from "../util/nullIfEmpty"
|
import nullIfEmpty from "../util/nullIfEmpty"
|
||||||
import { InterfaceReceipt } from "../interfaces/interfaceReceipt"
|
import { InterfaceReceipt } from "../interfaces/interfaceReceipt"
|
||||||
import { InterfacesReceipt as InterfacesReceipt } from "../interfaces/setupInterfaces"
|
import { InterfacesReceipt as InterfacesReceipt } from "../interfaces/setupInterfaces"
|
||||||
@@ -22,7 +21,6 @@ export type Save<
|
|||||||
> = (options: {
|
> = (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
input: ExtractConfigType<A> & Record<string, any>
|
input: ExtractConfigType<A> & Record<string, any>
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
dependencies: D.ConfigDependencies<Manifest>
|
dependencies: D.ConfigDependencies<Manifest>
|
||||||
}) => Promise<{
|
}) => Promise<{
|
||||||
dependenciesReceipt: DependenciesReceipt
|
dependenciesReceipt: DependenciesReceipt
|
||||||
@@ -38,7 +36,6 @@ export type Read<
|
|||||||
| Config<Record<string, any>, never>,
|
| Config<Record<string, any>, never>,
|
||||||
> = (options: {
|
> = (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<void | (ExtractConfigType<A> & Record<string, any>)>
|
}) => Promise<void | (ExtractConfigType<A> & Record<string, any>)>
|
||||||
/**
|
/**
|
||||||
* We want to setup a config export with a get and set, this
|
* We want to setup a config export with a get and set, this
|
||||||
@@ -72,7 +69,6 @@ export function setupConfig<
|
|||||||
const { restart } = await write({
|
const { restart } = await write({
|
||||||
input: JSON.parse(JSON.stringify(input)),
|
input: JSON.parse(JSON.stringify(input)),
|
||||||
effects,
|
effects,
|
||||||
utils: createUtils(effects),
|
|
||||||
dependencies: D.configDependenciesSet<Manifest>(),
|
dependencies: D.configDependenciesSet<Manifest>(),
|
||||||
})
|
})
|
||||||
if (restart) {
|
if (restart) {
|
||||||
@@ -80,14 +76,10 @@ export function setupConfig<
|
|||||||
}
|
}
|
||||||
}) as ExpectedExports.setConfig,
|
}) as ExpectedExports.setConfig,
|
||||||
getConfig: (async ({ effects }) => {
|
getConfig: (async ({ effects }) => {
|
||||||
const myUtils = createUtils<Manifest, Store>(effects)
|
const configValue = nullIfEmpty((await read({ effects })) || null)
|
||||||
const configValue = nullIfEmpty(
|
|
||||||
(await read({ effects, utils: myUtils })) || null,
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
spec: await spec.build({
|
spec: await spec.build({
|
||||||
effects,
|
effects,
|
||||||
utils: myUtils as any,
|
|
||||||
}),
|
}),
|
||||||
config: configValue,
|
config: configValue,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
DeepPartial,
|
DeepPartial,
|
||||||
Effects,
|
Effects,
|
||||||
} from "../types"
|
} from "../types"
|
||||||
import { Utils, createUtils } from "../util/utils"
|
|
||||||
import { deepEqual } from "../util/deepEqual"
|
import { deepEqual } from "../util/deepEqual"
|
||||||
import { deepMerge } from "../util/deepMerge"
|
import { deepMerge } from "../util/deepMerge"
|
||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
@@ -29,7 +28,6 @@ export class DependencyConfig<
|
|||||||
readonly dependencyConfig: (options: {
|
readonly dependencyConfig: (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
localConfig: Input
|
localConfig: Input
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<void | DeepPartial<RemoteConfig>>,
|
}) => Promise<void | DeepPartial<RemoteConfig>>,
|
||||||
readonly update: Update<
|
readonly update: Update<
|
||||||
void | DeepPartial<RemoteConfig>,
|
void | DeepPartial<RemoteConfig>,
|
||||||
@@ -41,7 +39,6 @@ export class DependencyConfig<
|
|||||||
return this.dependencyConfig({
|
return this.dependencyConfig({
|
||||||
localConfig: options.localConfig as Input,
|
localConfig: options.localConfig as Input,
|
||||||
effects: options.effects,
|
effects: options.effects,
|
||||||
utils: createUtils<Manifest, Store>(options.effects),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,52 +6,59 @@ import { Trigger } from "../trigger"
|
|||||||
import { TriggerInput } from "../trigger/TriggerInput"
|
import { TriggerInput } from "../trigger/TriggerInput"
|
||||||
import { defaultTrigger } from "../trigger/defaultTrigger"
|
import { defaultTrigger } from "../trigger/defaultTrigger"
|
||||||
import { once } from "../util/once"
|
import { once } from "../util/once"
|
||||||
|
import { Overlay } from "../util/Overlay"
|
||||||
|
|
||||||
export function healthCheck(o: {
|
export function healthCheck(o: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
name: string
|
name: string
|
||||||
|
imageId: string
|
||||||
trigger?: Trigger
|
trigger?: Trigger
|
||||||
fn(): Promise<CheckResult> | CheckResult
|
fn(overlay: Overlay): Promise<CheckResult> | CheckResult
|
||||||
onFirstSuccess?: () => unknown | Promise<unknown>
|
onFirstSuccess?: () => unknown | Promise<unknown>
|
||||||
}) {
|
}) {
|
||||||
new Promise(async () => {
|
new Promise(async () => {
|
||||||
let currentValue: TriggerInput = {
|
const overlay = await Overlay.of(o.effects, o.imageId)
|
||||||
hadSuccess: false,
|
try {
|
||||||
}
|
let currentValue: TriggerInput = {
|
||||||
const getCurrentValue = () => currentValue
|
hadSuccess: false,
|
||||||
const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue)
|
|
||||||
const triggerFirstSuccess = once(() =>
|
|
||||||
Promise.resolve(
|
|
||||||
"onFirstSuccess" in o && o.onFirstSuccess
|
|
||||||
? o.onFirstSuccess()
|
|
||||||
: undefined,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for (
|
|
||||||
let res = await trigger.next();
|
|
||||||
!res.done;
|
|
||||||
res = await trigger.next()
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const { status, message } = await o.fn()
|
|
||||||
await o.effects.setHealth({
|
|
||||||
name: o.name,
|
|
||||||
status,
|
|
||||||
message,
|
|
||||||
})
|
|
||||||
currentValue.hadSuccess = true
|
|
||||||
currentValue.lastResult = "passing"
|
|
||||||
await triggerFirstSuccess().catch((err) => {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
await o.effects.setHealth({
|
|
||||||
name: o.name,
|
|
||||||
status: "failure",
|
|
||||||
message: asMessage(e),
|
|
||||||
})
|
|
||||||
currentValue.lastResult = "failure"
|
|
||||||
}
|
}
|
||||||
|
const getCurrentValue = () => currentValue
|
||||||
|
const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue)
|
||||||
|
const triggerFirstSuccess = once(() =>
|
||||||
|
Promise.resolve(
|
||||||
|
"onFirstSuccess" in o && o.onFirstSuccess
|
||||||
|
? o.onFirstSuccess()
|
||||||
|
: undefined,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for (
|
||||||
|
let res = await trigger.next();
|
||||||
|
!res.done;
|
||||||
|
res = await trigger.next()
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { status, message } = await o.fn(overlay)
|
||||||
|
await o.effects.setHealth({
|
||||||
|
name: o.name,
|
||||||
|
status,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
currentValue.hadSuccess = true
|
||||||
|
currentValue.lastResult = "passing"
|
||||||
|
await triggerFirstSuccess().catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
await o.effects.setHealth({
|
||||||
|
name: o.name,
|
||||||
|
status: "failure",
|
||||||
|
message: asMessage(e),
|
||||||
|
})
|
||||||
|
currentValue.lastResult = "failure"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await overlay.destroy()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return {} as HealthReceipt
|
return {} as HealthReceipt
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { Effects } from "../../types"
|
import { Effects } from "../../types"
|
||||||
import { createUtils } from "../../util"
|
|
||||||
import { stringFromStdErrOut } from "../../util/stringFromStdErrOut"
|
import { stringFromStdErrOut } from "../../util/stringFromStdErrOut"
|
||||||
import { CheckResult } from "./CheckResult"
|
import { CheckResult } from "./CheckResult"
|
||||||
|
|
||||||
|
import { promisify } from "node:util"
|
||||||
|
import * as CP from "node:child_process"
|
||||||
|
|
||||||
|
const cpExec = promisify(CP.exec)
|
||||||
|
const cpExecFile = promisify(CP.execFile)
|
||||||
export function containsAddress(x: string, port: number) {
|
export function containsAddress(x: string, port: number) {
|
||||||
const readPorts = x
|
const readPorts = x
|
||||||
.split("\n")
|
.split("\n")
|
||||||
@@ -28,20 +33,15 @@ export async function checkPortListening(
|
|||||||
timeout?: number
|
timeout?: number
|
||||||
},
|
},
|
||||||
): Promise<CheckResult> {
|
): Promise<CheckResult> {
|
||||||
const utils = createUtils(effects)
|
|
||||||
return Promise.race<CheckResult>([
|
return Promise.race<CheckResult>([
|
||||||
Promise.resolve().then(async () => {
|
Promise.resolve().then(async () => {
|
||||||
const hasAddress =
|
const hasAddress =
|
||||||
containsAddress(
|
containsAddress(
|
||||||
await utils.childProcess
|
await cpExec(`cat /proc/net/tcp`, {}).then(stringFromStdErrOut),
|
||||||
.exec(`cat /proc/net/tcp`, {})
|
|
||||||
.then(stringFromStdErrOut),
|
|
||||||
port,
|
port,
|
||||||
) ||
|
) ||
|
||||||
containsAddress(
|
containsAddress(
|
||||||
await utils.childProcess
|
await cpExec("cat /proc/net/udp", {}).then(stringFromStdErrOut),
|
||||||
.exec("cat /proc/net/udp", {})
|
|
||||||
.then(stringFromStdErrOut),
|
|
||||||
port,
|
port,
|
||||||
)
|
)
|
||||||
if (hasAddress) {
|
if (hasAddress) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CommandType, Effects } from "../../types"
|
import { Effects } from "../../types"
|
||||||
import { createUtils } from "../../util"
|
import { Overlay } from "../../util/Overlay"
|
||||||
import { stringFromStdErrOut } from "../../util/stringFromStdErrOut"
|
import { stringFromStdErrOut } from "../../util/stringFromStdErrOut"
|
||||||
import { CheckResult } from "./CheckResult"
|
import { CheckResult } from "./CheckResult"
|
||||||
import { timeoutPromise } from "./index"
|
import { timeoutPromise } from "./index"
|
||||||
@@ -13,7 +13,8 @@ import { timeoutPromise } from "./index"
|
|||||||
*/
|
*/
|
||||||
export const runHealthScript = async (
|
export const runHealthScript = async (
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
runCommand: string,
|
runCommand: string[],
|
||||||
|
overlay: Overlay,
|
||||||
{
|
{
|
||||||
timeout = 30000,
|
timeout = 30000,
|
||||||
errorMessage = `Error while running command: ${runCommand}`,
|
errorMessage = `Error while running command: ${runCommand}`,
|
||||||
@@ -21,9 +22,8 @@ export const runHealthScript = async (
|
|||||||
`Have ran script ${runCommand} and the result: ${res}`,
|
`Have ran script ${runCommand} and the result: ${res}`,
|
||||||
} = {},
|
} = {},
|
||||||
): Promise<CheckResult> => {
|
): Promise<CheckResult> => {
|
||||||
const utils = createUtils(effects)
|
|
||||||
const res = await Promise.race([
|
const res = await Promise.race([
|
||||||
utils.childProcess.exec(runCommand, { timeout }).then(stringFromStdErrOut),
|
overlay.exec(runCommand),
|
||||||
timeoutPromise(timeout),
|
timeoutPromise(timeout),
|
||||||
]).catch((e) => {
|
]).catch((e) => {
|
||||||
console.warn(errorMessage)
|
console.warn(errorMessage)
|
||||||
@@ -33,6 +33,6 @@ export const runHealthScript = async (
|
|||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
status: "passing",
|
status: "passing",
|
||||||
message: message(res),
|
message: message(res.stdout.toString()),
|
||||||
} as CheckResult
|
} as CheckResult
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export { Daemons } from "./mainFn/Daemons"
|
export { Daemons } from "./mainFn/Daemons"
|
||||||
export { EmVer } from "./emverLite/mod"
|
export { EmVer } from "./emverLite/mod"
|
||||||
export { Overlay } from "./util/Overlay"
|
export { Overlay } from "./util/Overlay"
|
||||||
export { Utils } from "./util/utils"
|
|
||||||
export { StartSdk } from "./StartSdk"
|
export { StartSdk } from "./StartSdk"
|
||||||
export { setupManifest } from "./manifest/setupManifest"
|
export { setupManifest } from "./manifest/setupManifest"
|
||||||
|
export { FileHelper } from "./util/fileHelper"
|
||||||
export * as actions from "./actions"
|
export * as actions from "./actions"
|
||||||
export * as backup from "./backup"
|
export * as backup from "./backup"
|
||||||
export * as config from "./config"
|
export * as config from "./config"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ManifestVersion, SDKManifest } from "../../manifest/ManifestTypes"
|
import { ManifestVersion, SDKManifest } from "../../manifest/ManifestTypes"
|
||||||
import { Effects } from "../../types"
|
import { Effects } from "../../types"
|
||||||
import { Utils } from "../../util/utils"
|
|
||||||
|
|
||||||
export class Migration<
|
export class Migration<
|
||||||
Manifest extends SDKManifest,
|
Manifest extends SDKManifest,
|
||||||
@@ -10,14 +9,8 @@ export class Migration<
|
|||||||
constructor(
|
constructor(
|
||||||
readonly options: {
|
readonly options: {
|
||||||
version: Version
|
version: Version
|
||||||
up: (opts: {
|
up: (opts: { effects: Effects }) => Promise<void>
|
||||||
effects: Effects
|
down: (opts: { effects: Effects }) => Promise<void>
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<void>
|
|
||||||
down: (opts: {
|
|
||||||
effects: Effects
|
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<void>
|
|
||||||
},
|
},
|
||||||
) {}
|
) {}
|
||||||
static of<
|
static of<
|
||||||
@@ -26,23 +19,17 @@ export class Migration<
|
|||||||
Version extends ManifestVersion,
|
Version extends ManifestVersion,
|
||||||
>(options: {
|
>(options: {
|
||||||
version: Version
|
version: Version
|
||||||
up: (opts: {
|
up: (opts: { effects: Effects }) => Promise<void>
|
||||||
effects: Effects
|
down: (opts: { effects: Effects }) => Promise<void>
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<void>
|
|
||||||
down: (opts: {
|
|
||||||
effects: Effects
|
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<void>
|
|
||||||
}) {
|
}) {
|
||||||
return new Migration<Manifest, Store, Version>(options)
|
return new Migration<Manifest, Store, Version>(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async up(opts: { effects: Effects; utils: Utils<Manifest, Store> }) {
|
async up(opts: { effects: Effects }) {
|
||||||
this.up(opts)
|
this.up(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
async down(opts: { effects: Effects; utils: Utils<Manifest, Store> }) {
|
async down(opts: { effects: Effects }) {
|
||||||
this.down(opts)
|
this.down(opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { EmVer } from "../../emverLite/mod"
|
import { EmVer } from "../../emverLite/mod"
|
||||||
import { SDKManifest } from "../../manifest/ManifestTypes"
|
import { SDKManifest } from "../../manifest/ManifestTypes"
|
||||||
import { ExpectedExports } from "../../types"
|
import { ExpectedExports } from "../../types"
|
||||||
import { createUtils } from "../../util"
|
|
||||||
import { once } from "../../util/once"
|
import { once } from "../../util/once"
|
||||||
import { Migration } from "./Migration"
|
import { Migration } from "./Migration"
|
||||||
|
|
||||||
@@ -32,13 +31,12 @@ export class Migrations<Manifest extends SDKManifest, Store> {
|
|||||||
effects,
|
effects,
|
||||||
previousVersion,
|
previousVersion,
|
||||||
}: Parameters<ExpectedExports.init>[0]) {
|
}: Parameters<ExpectedExports.init>[0]) {
|
||||||
const utils = createUtils<Manifest, Store>(effects)
|
|
||||||
if (!!previousVersion) {
|
if (!!previousVersion) {
|
||||||
const previousVersionEmVer = EmVer.parse(previousVersion)
|
const previousVersionEmVer = EmVer.parse(previousVersion)
|
||||||
for (const [_, migration] of this.sortedMigrations()
|
for (const [_, migration] of this.sortedMigrations()
|
||||||
.filter((x) => x[0].greaterThan(previousVersionEmVer))
|
.filter((x) => x[0].greaterThan(previousVersionEmVer))
|
||||||
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
|
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
|
||||||
await migration.up({ effects, utils })
|
await migration.up({ effects })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,14 +44,13 @@ export class Migrations<Manifest extends SDKManifest, Store> {
|
|||||||
effects,
|
effects,
|
||||||
nextVersion,
|
nextVersion,
|
||||||
}: Parameters<ExpectedExports.uninit>[0]) {
|
}: Parameters<ExpectedExports.uninit>[0]) {
|
||||||
const utils = createUtils<Manifest, Store>(effects)
|
|
||||||
if (!!nextVersion) {
|
if (!!nextVersion) {
|
||||||
const nextVersionEmVer = EmVer.parse(nextVersion)
|
const nextVersionEmVer = EmVer.parse(nextVersion)
|
||||||
const reversed = [...this.sortedMigrations()].reverse()
|
const reversed = [...this.sortedMigrations()].reverse()
|
||||||
for (const [_, migration] of reversed
|
for (const [_, migration] of reversed
|
||||||
.filter((x) => x[0].greaterThan(nextVersionEmVer))
|
.filter((x) => x[0].greaterThan(nextVersionEmVer))
|
||||||
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
|
.filter((x) => x[0].lessThanOrEqual(this.currentVersion()))) {
|
||||||
await migration.down({ effects, utils })
|
await migration.down({ effects })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import { Effects, ExposeServicePaths, ExposeUiPaths } from "../types"
|
import { Effects, ExposeServicePaths, ExposeUiPaths } from "../types"
|
||||||
import { Utils } from "../util/utils"
|
|
||||||
|
|
||||||
export type SetupExports<Store> = (opts: {
|
export type SetupExports<Store> = (opts: { effects: Effects }) =>
|
||||||
effects: Effects
|
|
||||||
utils: Utils<any, Store>
|
|
||||||
}) =>
|
|
||||||
| {
|
| {
|
||||||
ui: ExposeUiPaths<Store>
|
ui: { [k: string]: ExposeUiPaths<Store> }
|
||||||
services: ExposeServicePaths<Store>
|
services: ExposeServicePaths<Store>
|
||||||
}
|
}
|
||||||
| Promise<{
|
| Promise<{
|
||||||
ui: ExposeUiPaths<Store>
|
ui: { [k: string]: ExposeUiPaths<Store> }
|
||||||
services: ExposeServicePaths<Store>
|
services: ExposeServicePaths<Store>
|
||||||
}>
|
}>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { SetInterfaces } from "../interfaces/setupInterfaces"
|
import { SetInterfaces } from "../interfaces/setupInterfaces"
|
||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
import { ExpectedExports } from "../types"
|
import { ExpectedExports, ExposeUiPaths, ExposeUiPathsAll } from "../types"
|
||||||
import { createUtils } from "../util"
|
|
||||||
import { Migrations } from "./migrations/setupMigrations"
|
import { Migrations } from "./migrations/setupMigrations"
|
||||||
import { SetupExports } from "./setupExports"
|
import { SetupExports } from "./setupExports"
|
||||||
import { Install } from "./setupInstall"
|
import { Install } from "./setupInstall"
|
||||||
@@ -19,20 +18,20 @@ export function setupInit<Manifest extends SDKManifest, Store>(
|
|||||||
} {
|
} {
|
||||||
return {
|
return {
|
||||||
init: async (opts) => {
|
init: async (opts) => {
|
||||||
const utils = createUtils<Manifest, Store>(opts.effects)
|
|
||||||
await migrations.init(opts)
|
await migrations.init(opts)
|
||||||
await install.init(opts)
|
await install.init(opts)
|
||||||
await setInterfaces({
|
await setInterfaces({
|
||||||
...opts,
|
...opts,
|
||||||
input: null,
|
input: null,
|
||||||
utils,
|
|
||||||
})
|
|
||||||
const { services, ui } = await setupExports({
|
|
||||||
...opts,
|
|
||||||
utils,
|
|
||||||
})
|
})
|
||||||
|
const { services, ui } = await setupExports(opts)
|
||||||
await opts.effects.exposeForDependents(services)
|
await opts.effects.exposeForDependents(services)
|
||||||
await opts.effects.exposeUi({ paths: ui })
|
await opts.effects.exposeUi(
|
||||||
|
forExpose({
|
||||||
|
type: "object",
|
||||||
|
value: ui,
|
||||||
|
}),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
uninit: async (opts) => {
|
uninit: async (opts) => {
|
||||||
await migrations.uninit(opts)
|
await migrations.uninit(opts)
|
||||||
@@ -40,3 +39,21 @@ export function setupInit<Manifest extends SDKManifest, Store>(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function forExpose<Store>(ui: ExposeUiPaths<Store>): ExposeUiPathsAll {
|
||||||
|
if (ui.type === ("object" as const)) {
|
||||||
|
return {
|
||||||
|
type: "object" as const,
|
||||||
|
value: Object.fromEntries(
|
||||||
|
Object.entries(ui.value).map(([key, value]) => [key, forExpose(value)]),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
description: null,
|
||||||
|
|
||||||
|
copyable: null,
|
||||||
|
qr: null,
|
||||||
|
...ui,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
import { Effects, ExpectedExports } from "../types"
|
import { Effects, ExpectedExports } from "../types"
|
||||||
import { Utils, createUtils } from "../util/utils"
|
|
||||||
|
|
||||||
export type InstallFn<Manifest extends SDKManifest, Store> = (opts: {
|
export type InstallFn<Manifest extends SDKManifest, Store> = (opts: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
export class Install<Manifest extends SDKManifest, Store> {
|
export class Install<Manifest extends SDKManifest, Store> {
|
||||||
private constructor(readonly fn: InstallFn<Manifest, Store>) {}
|
private constructor(readonly fn: InstallFn<Manifest, Store>) {}
|
||||||
@@ -21,7 +19,6 @@ export class Install<Manifest extends SDKManifest, Store> {
|
|||||||
if (!previousVersion)
|
if (!previousVersion)
|
||||||
await this.fn({
|
await this.fn({
|
||||||
effects,
|
effects,
|
||||||
utils: createUtils(effects),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
import { Effects, ExpectedExports } from "../types"
|
import { Effects, ExpectedExports } from "../types"
|
||||||
import { Utils, createUtils } from "../util/utils"
|
|
||||||
|
|
||||||
export type UninstallFn<Manifest extends SDKManifest, Store> = (opts: {
|
export type UninstallFn<Manifest extends SDKManifest, Store> = (opts: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
export class Uninstall<Manifest extends SDKManifest, Store> {
|
export class Uninstall<Manifest extends SDKManifest, Store> {
|
||||||
private constructor(readonly fn: UninstallFn<Manifest, Store>) {}
|
private constructor(readonly fn: UninstallFn<Manifest, Store>) {}
|
||||||
@@ -21,7 +19,6 @@ export class Uninstall<Manifest extends SDKManifest, Store> {
|
|||||||
if (!nextVersion)
|
if (!nextVersion)
|
||||||
await this.fn({
|
await this.fn({
|
||||||
effects,
|
effects,
|
||||||
utils: createUtils(effects),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,12 +59,13 @@ type AddSslOptions = {
|
|||||||
preferredExternalPort: number
|
preferredExternalPort: number
|
||||||
addXForwardedHeaders: boolean | null /** default: false */
|
addXForwardedHeaders: boolean | null /** default: false */
|
||||||
}
|
}
|
||||||
type Security = { secure: false; ssl: false } | { secure: true; ssl: boolean }
|
type Security = { ssl: boolean }
|
||||||
export type BindOptions = {
|
export type BindOptions = {
|
||||||
scheme: Scheme
|
scheme: Scheme
|
||||||
preferredExternalPort: number
|
preferredExternalPort: number
|
||||||
addSsl: AddSslOptions | null
|
addSsl: AddSslOptions | null
|
||||||
} & Security
|
secure: Security | null
|
||||||
|
}
|
||||||
type KnownProtocols = typeof knownProtocols
|
type KnownProtocols = typeof knownProtocols
|
||||||
type ProtocolsWithSslVariants = {
|
type ProtocolsWithSslVariants = {
|
||||||
[K in keyof KnownProtocols]: KnownProtocols[K] extends {
|
[K in keyof KnownProtocols]: KnownProtocols[K] extends {
|
||||||
@@ -79,16 +80,17 @@ type NotProtocolsWithSslVariants = Exclude<
|
|||||||
>
|
>
|
||||||
|
|
||||||
type BindOptionsByKnownProtocol =
|
type BindOptionsByKnownProtocol =
|
||||||
| ({
|
| {
|
||||||
protocol: ProtocolsWithSslVariants
|
protocol: ProtocolsWithSslVariants
|
||||||
preferredExternalPort?: number
|
preferredExternalPort?: number
|
||||||
scheme: Scheme | null
|
scheme?: Scheme
|
||||||
} & ({ noAddSsl: true } | { addSsl?: Partial<AddSslOptions> }))
|
addSsl?: Partial<AddSslOptions>
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
protocol: NotProtocolsWithSslVariants
|
protocol: NotProtocolsWithSslVariants
|
||||||
preferredExternalPort?: number
|
preferredExternalPort?: number
|
||||||
scheme: Scheme | null
|
scheme?: Scheme
|
||||||
addSsl: AddSslOptions | null
|
addSsl?: AddSslOptions
|
||||||
}
|
}
|
||||||
type BindOptionsByProtocol = BindOptionsByKnownProtocol | BindOptions
|
type BindOptionsByProtocol = BindOptionsByKnownProtocol | BindOptions
|
||||||
|
|
||||||
@@ -120,17 +122,12 @@ export class Host {
|
|||||||
|
|
||||||
private async bindPortForUnknown(
|
private async bindPortForUnknown(
|
||||||
internalPort: number,
|
internalPort: number,
|
||||||
options:
|
options: {
|
||||||
| ({
|
scheme: Scheme
|
||||||
scheme: Scheme
|
preferredExternalPort: number
|
||||||
preferredExternalPort: number
|
addSsl: AddSslOptions | null
|
||||||
addSsl: AddSslOptions | null
|
secure: { ssl: boolean } | null
|
||||||
} & { secure: false; ssl: false })
|
},
|
||||||
| ({
|
|
||||||
scheme: Scheme
|
|
||||||
preferredExternalPort: number
|
|
||||||
addSsl: AddSslOptions | null
|
|
||||||
} & { secure: true; ssl: boolean }),
|
|
||||||
) {
|
) {
|
||||||
await this.options.effects.bind({
|
await this.options.effects.bind({
|
||||||
kind: this.options.kind,
|
kind: this.options.kind,
|
||||||
@@ -154,18 +151,13 @@ export class Host {
|
|||||||
knownProtocols[options.protocol].defaultPort
|
knownProtocols[options.protocol].defaultPort
|
||||||
const addSsl = this.getAddSsl(options, protoInfo)
|
const addSsl = this.getAddSsl(options, protoInfo)
|
||||||
|
|
||||||
const security: Security = !protoInfo.secure
|
const secure: Security | null = !protoInfo.secure ? null : { ssl: false }
|
||||||
? {
|
|
||||||
secure: protoInfo.secure,
|
|
||||||
ssl: protoInfo.ssl,
|
|
||||||
}
|
|
||||||
: { secure: false, ssl: false }
|
|
||||||
|
|
||||||
const newOptions = {
|
const newOptions = {
|
||||||
scheme,
|
scheme,
|
||||||
preferredExternalPort,
|
preferredExternalPort,
|
||||||
addSsl,
|
addSsl,
|
||||||
...security,
|
secure,
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.options.effects.bind({
|
await this.options.effects.bind({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { ServiceInterfaceType } from "../StartSdk"
|
||||||
import { Effects } from "../types"
|
import { Effects } from "../types"
|
||||||
import { ServiceInterfaceType } from "../util/utils"
|
|
||||||
import { Scheme } from "./Host"
|
import { Scheme } from "./Host"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Config } from "../config/builder/config"
|
import { Config } from "../config/builder/config"
|
||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
import { AddressInfo, Effects } from "../types"
|
import { AddressInfo, Effects } from "../types"
|
||||||
import { Utils } from "../util/utils"
|
|
||||||
import { AddressReceipt } from "./AddressReceipt"
|
import { AddressReceipt } from "./AddressReceipt"
|
||||||
|
|
||||||
export type InterfacesReceipt = Array<AddressInfo[] & AddressReceipt>
|
export type InterfacesReceipt = Array<AddressInfo[] & AddressReceipt>
|
||||||
@@ -10,11 +9,7 @@ export type SetInterfaces<
|
|||||||
Store,
|
Store,
|
||||||
ConfigInput extends Record<string, any>,
|
ConfigInput extends Record<string, any>,
|
||||||
Output extends InterfacesReceipt,
|
Output extends InterfacesReceipt,
|
||||||
> = (opts: {
|
> = (opts: { effects: Effects; input: null | ConfigInput }) => Promise<Output>
|
||||||
effects: Effects
|
|
||||||
input: null | ConfigInput
|
|
||||||
utils: Utils<Manifest, Store>
|
|
||||||
}) => Promise<Output>
|
|
||||||
export type SetupInterfaces = <
|
export type SetupInterfaces = <
|
||||||
Manifest extends SDKManifest,
|
Manifest extends SDKManifest,
|
||||||
Store,
|
Store,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { NO_TIMEOUT, SIGKILL, SIGTERM, Signals } from "../StartSdk"
|
||||||
import { HealthReceipt } from "../health/HealthReceipt"
|
import { HealthReceipt } from "../health/HealthReceipt"
|
||||||
import { CheckResult } from "../health/checkFns"
|
import { CheckResult } from "../health/checkFns"
|
||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
@@ -5,9 +6,22 @@ import { Trigger } from "../trigger"
|
|||||||
import { TriggerInput } from "../trigger/TriggerInput"
|
import { TriggerInput } from "../trigger/TriggerInput"
|
||||||
import { defaultTrigger } from "../trigger/defaultTrigger"
|
import { defaultTrigger } from "../trigger/defaultTrigger"
|
||||||
import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types"
|
import { DaemonReturned, Effects, ValidIfNoStupidEscape } from "../types"
|
||||||
import { createUtils } from "../util"
|
|
||||||
import { Signals } from "../util/utils"
|
|
||||||
import { Mounts } from "./Mounts"
|
import { Mounts } from "./Mounts"
|
||||||
|
import { CommandOptions, MountOptions, Overlay } from "../util/Overlay"
|
||||||
|
import { splitCommand } from "../util/splitCommand"
|
||||||
|
|
||||||
|
import { promisify } from "node:util"
|
||||||
|
import * as CP from "node:child_process"
|
||||||
|
|
||||||
|
const cpExec = promisify(CP.exec)
|
||||||
|
const cpExecFile = promisify(CP.execFile)
|
||||||
|
async function psTree(pid: number, overlay: Overlay): Promise<number[]> {
|
||||||
|
const { stdout } = await cpExec(`pstree -p ${pid}`)
|
||||||
|
const regex: RegExp = /\((\d+)\)/g
|
||||||
|
return [...stdout.toString().matchAll(regex)].map(([_all, pid]) =>
|
||||||
|
parseInt(pid),
|
||||||
|
)
|
||||||
|
}
|
||||||
type Daemon<
|
type Daemon<
|
||||||
Manifest extends SDKManifest,
|
Manifest extends SDKManifest,
|
||||||
Ids extends string,
|
Ids extends string,
|
||||||
@@ -28,6 +42,89 @@ type Daemon<
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`
|
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`
|
||||||
|
|
||||||
|
const runDaemon =
|
||||||
|
<Manifest extends SDKManifest>() =>
|
||||||
|
async <A extends string>(
|
||||||
|
effects: Effects,
|
||||||
|
imageId: Manifest["images"][number],
|
||||||
|
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
||||||
|
options: CommandOptions & {
|
||||||
|
mounts?: { path: string; options: MountOptions }[]
|
||||||
|
overlay?: Overlay
|
||||||
|
},
|
||||||
|
): Promise<DaemonReturned> => {
|
||||||
|
const commands = splitCommand(command)
|
||||||
|
const overlay = options.overlay || (await Overlay.of(effects, imageId))
|
||||||
|
for (let mount of options.mounts || []) {
|
||||||
|
await overlay.mount(mount.options, mount.path)
|
||||||
|
}
|
||||||
|
const childProcess = await overlay.spawn(commands, {
|
||||||
|
env: options.env,
|
||||||
|
})
|
||||||
|
const answer = new Promise<null>((resolve, reject) => {
|
||||||
|
childProcess.stdout.on("data", (data: any) => {
|
||||||
|
console.log(data.toString())
|
||||||
|
})
|
||||||
|
childProcess.stderr.on("data", (data: any) => {
|
||||||
|
console.error(data.toString())
|
||||||
|
})
|
||||||
|
|
||||||
|
childProcess.on("exit", (code: any) => {
|
||||||
|
if (code === 0) {
|
||||||
|
return resolve(null)
|
||||||
|
}
|
||||||
|
return reject(new Error(`${commands[0]} exited with code ${code}`))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const pid = childProcess.pid
|
||||||
|
return {
|
||||||
|
async wait() {
|
||||||
|
const pids = pid ? await psTree(pid, overlay) : []
|
||||||
|
try {
|
||||||
|
return await answer
|
||||||
|
} finally {
|
||||||
|
for (const process of pids) {
|
||||||
|
cpExecFile("kill", [`-9`, String(process)]).catch((_) => {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async term({ signal = SIGTERM, timeout = NO_TIMEOUT } = {}) {
|
||||||
|
const pids = pid ? await psTree(pid, overlay) : []
|
||||||
|
try {
|
||||||
|
childProcess.kill(signal)
|
||||||
|
|
||||||
|
if (timeout > NO_TIMEOUT) {
|
||||||
|
const didTimeout = await Promise.race([
|
||||||
|
new Promise((resolve) => setTimeout(resolve, timeout)).then(
|
||||||
|
() => true,
|
||||||
|
),
|
||||||
|
answer.then(() => false),
|
||||||
|
])
|
||||||
|
if (didTimeout) {
|
||||||
|
childProcess.kill(SIGKILL)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await answer
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await overlay.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const process of pids) {
|
||||||
|
await cpExecFile("kill", [`-${signal}`, String(process)])
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
for (const process of pids) {
|
||||||
|
cpExecFile("kill", [`-9`, String(process)]).catch((_) => {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for defining and controlling the service daemons
|
* A class for defining and controlling the service daemons
|
||||||
```ts
|
```ts
|
||||||
@@ -106,9 +203,8 @@ export class Daemons<Manifest extends SDKManifest, Ids extends string> {
|
|||||||
)
|
)
|
||||||
daemonsStarted[daemon.id] = requiredPromise.then(async () => {
|
daemonsStarted[daemon.id] = requiredPromise.then(async () => {
|
||||||
const { command, imageId } = daemon
|
const { command, imageId } = daemon
|
||||||
const utils = createUtils<Manifest>(effects)
|
|
||||||
|
|
||||||
const child = utils.runDaemon(imageId, command, {
|
const child = runDaemon<Manifest>()(effects, imageId, command, {
|
||||||
env: daemon.env,
|
env: daemon.env,
|
||||||
mounts: daemon.mounts.build(),
|
mounts: daemon.mounts.build(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Effects, ExpectedExports } from "../types"
|
import { ExpectedExports } from "../types"
|
||||||
import { createMainUtils } from "../util"
|
|
||||||
import { Utils, createUtils } from "../util/utils"
|
|
||||||
import { Daemons } from "./Daemons"
|
import { Daemons } from "./Daemons"
|
||||||
import "../interfaces/ServiceInterfaceBuilder"
|
import "../interfaces/ServiceInterfaceBuilder"
|
||||||
import "../interfaces/Origin"
|
import "../interfaces/Origin"
|
||||||
|
|
||||||
import "./Daemons"
|
import "./Daemons"
|
||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
|
import { MainEffects } from "../StartSdk"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to ensure that the main function is running with the valid proofs.
|
* Used to ensure that the main function is running with the valid proofs.
|
||||||
@@ -20,16 +19,12 @@ import { SDKManifest } from "../manifest/ManifestTypes"
|
|||||||
*/
|
*/
|
||||||
export const setupMain = <Manifest extends SDKManifest, Store>(
|
export const setupMain = <Manifest extends SDKManifest, Store>(
|
||||||
fn: (o: {
|
fn: (o: {
|
||||||
effects: Effects
|
effects: MainEffects
|
||||||
started(onTerm: () => PromiseLike<void>): PromiseLike<void>
|
started(onTerm: () => PromiseLike<void>): PromiseLike<void>
|
||||||
utils: Utils<Manifest, Store, {}>
|
|
||||||
}) => Promise<Daemons<Manifest, any>>,
|
}) => Promise<Daemons<Manifest, any>>,
|
||||||
): ExpectedExports.main => {
|
): ExpectedExports.main => {
|
||||||
return async (options) => {
|
return async (options) => {
|
||||||
const result = await fn({
|
const result = await fn(options)
|
||||||
...options,
|
|
||||||
utils: createMainUtils<Manifest, Store>(options.effects),
|
|
||||||
})
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { List } from "../config/builder/list"
|
|||||||
import { Value } from "../config/builder/value"
|
import { Value } from "../config/builder/value"
|
||||||
import { Variants } from "../config/builder/variants"
|
import { Variants } from "../config/builder/variants"
|
||||||
import { ValueSpec } from "../config/configTypes"
|
import { ValueSpec } from "../config/configTypes"
|
||||||
|
import { setupManifest } from "../manifest/setupManifest"
|
||||||
|
import { StartSdk } from "../StartSdk"
|
||||||
|
|
||||||
describe("builder tests", () => {
|
describe("builder tests", () => {
|
||||||
test("text", async () => {
|
test("text", async () => {
|
||||||
@@ -379,17 +381,61 @@ describe("values", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
test("datetime", async () => {
|
test("datetime", async () => {
|
||||||
const value = Value.dynamicDatetime<{ test: "a" }>(async ({ utils }) => {
|
const sdk = StartSdk.of()
|
||||||
;async () => {
|
.withManifest(
|
||||||
;(await utils.store.getOwn("/test").once()) satisfies "a"
|
setupManifest({
|
||||||
}
|
id: "testOutput",
|
||||||
|
title: "",
|
||||||
|
version: "1.0",
|
||||||
|
releaseNotes: "",
|
||||||
|
license: "",
|
||||||
|
replaces: [],
|
||||||
|
wrapperRepo: "",
|
||||||
|
upstreamRepo: "",
|
||||||
|
supportSite: "",
|
||||||
|
marketingSite: "",
|
||||||
|
donationUrl: null,
|
||||||
|
description: {
|
||||||
|
short: "",
|
||||||
|
long: "",
|
||||||
|
},
|
||||||
|
containers: {},
|
||||||
|
images: [],
|
||||||
|
volumes: [],
|
||||||
|
assets: [],
|
||||||
|
alerts: {
|
||||||
|
install: null,
|
||||||
|
update: null,
|
||||||
|
uninstall: null,
|
||||||
|
restore: null,
|
||||||
|
start: null,
|
||||||
|
stop: null,
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
remoteTest: {
|
||||||
|
description: "",
|
||||||
|
requirement: { how: "", type: "opt-in" },
|
||||||
|
version: "1.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.withStore<{ test: "a" }>()
|
||||||
|
.build(true)
|
||||||
|
|
||||||
return {
|
const value = Value.dynamicDatetime<{ test: "a" }>(
|
||||||
name: "Testing",
|
async ({ effects }) => {
|
||||||
required: { default: null },
|
;async () => {
|
||||||
inputmode: "date",
|
;(await sdk.store.getOwn(effects, "/test").once()) satisfies "a"
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
return {
|
||||||
|
name: "Testing",
|
||||||
|
required: { default: null },
|
||||||
|
inputmode: "date",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
const validator = value.validator
|
const validator = value.validator
|
||||||
validator.unsafeCast("2021-01-01")
|
validator.unsafeCast("2021-01-01")
|
||||||
validator.unsafeCast(null)
|
validator.unsafeCast(null)
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import { ServiceInterfaceBuilder } from "../interfaces/ServiceInterfaceBuilder"
|
import { ServiceInterfaceBuilder } from "../interfaces/ServiceInterfaceBuilder"
|
||||||
import { Effects } from "../types"
|
import { Effects } from "../types"
|
||||||
import { createUtils } from "../util"
|
import { sdk } from "./output.sdk"
|
||||||
|
|
||||||
describe("host", () => {
|
describe("host", () => {
|
||||||
test("Testing that the types work", () => {
|
test("Testing that the types work", () => {
|
||||||
async function test(effects: Effects) {
|
async function test(effects: Effects) {
|
||||||
const utils = createUtils<never, never>(effects)
|
const foo = sdk.host.multi(effects, "foo")
|
||||||
const foo = utils.host.multi("foo")
|
|
||||||
const fooOrigin = await foo.bindPort(80, {
|
const fooOrigin = await foo.bindPort(80, {
|
||||||
protocol: "http" as const,
|
protocol: "http" as const,
|
||||||
scheme: null,
|
|
||||||
})
|
})
|
||||||
const fooInterface = new ServiceInterfaceBuilder({
|
const fooInterface = new ServiceInterfaceBuilder({
|
||||||
effects,
|
effects,
|
||||||
|
|||||||
@@ -13,40 +13,25 @@ import { ExposeUiParams } from "../../../core/startos/bindings/ExposeUiParams"
|
|||||||
import { GetSslCertificateParams } from "../../../core/startos/bindings/GetSslCertificateParams"
|
import { GetSslCertificateParams } from "../../../core/startos/bindings/GetSslCertificateParams"
|
||||||
import { GetSslKeyParams } from "../../../core/startos/bindings/GetSslKeyParams"
|
import { GetSslKeyParams } from "../../../core/startos/bindings/GetSslKeyParams"
|
||||||
import { GetServiceInterfaceParams } from "../../../core/startos/bindings/GetServiceInterfaceParams"
|
import { GetServiceInterfaceParams } from "../../../core/startos/bindings/GetServiceInterfaceParams"
|
||||||
|
import { SetDependenciesParams } from "../../../core/startos/bindings/SetDependenciesParams"
|
||||||
|
import { GetSystemSmtpParams } from "../../../core/startos/bindings/GetSystemSmtpParams"
|
||||||
|
import { GetServicePortForwardParams } from "../../../core/startos/bindings/GetServicePortForwardParams"
|
||||||
|
import { ExportServiceInterfaceParams } from "../../../core/startos/bindings/ExportServiceInterfaceParams"
|
||||||
|
import { GetPrimaryUrlParams } from "../../../core/startos/bindings/GetPrimaryUrlParams"
|
||||||
|
import { ListServiceInterfacesParams } from "../../../core/startos/bindings/ListServiceInterfacesParams"
|
||||||
|
import { RemoveAddressParams } from "../../../core/startos/bindings/RemoveAddressParams"
|
||||||
|
import { ExportActionParams } from "../../../core/startos/bindings/ExportActionParams"
|
||||||
|
import { RemoveActionParams } from "../../../core/startos/bindings/RemoveActionParams"
|
||||||
|
import { ReverseProxyParams } from "../../../core/startos/bindings/ReverseProxyParams"
|
||||||
|
import { MountParams } from "../../../core/startos/bindings/MountParams"
|
||||||
function typeEquality<ExpectedType>(_a: ExpectedType) {}
|
function typeEquality<ExpectedType>(_a: ExpectedType) {}
|
||||||
describe("startosTypeValidation ", () => {
|
describe("startosTypeValidation ", () => {
|
||||||
test(`checking the params match`, () => {
|
test(`checking the params match`, () => {
|
||||||
const testInput: any = {}
|
const testInput: any = {}
|
||||||
typeEquality<{
|
typeEquality<{
|
||||||
[K in keyof Effects &
|
[K in keyof Effects]: Effects[K] extends (args: infer A) => any
|
||||||
(
|
? A
|
||||||
| "gitInfo"
|
: never
|
||||||
| "echo"
|
|
||||||
| "chroot"
|
|
||||||
| "exists"
|
|
||||||
| "executeAction"
|
|
||||||
| "getConfigured"
|
|
||||||
| "stopped"
|
|
||||||
| "running"
|
|
||||||
| "restart"
|
|
||||||
| "shutdown"
|
|
||||||
| "setConfigured"
|
|
||||||
| "setMainStatus"
|
|
||||||
| "setHealth"
|
|
||||||
| "getStore"
|
|
||||||
| "setStore"
|
|
||||||
| "exposeForDependents"
|
|
||||||
| "exposeUi"
|
|
||||||
| "createOverlayedImage"
|
|
||||||
| "destroyOverlayedImage"
|
|
||||||
| "getSslCertificate"
|
|
||||||
| "getSslKey"
|
|
||||||
| "getServiceInterface"
|
|
||||||
| "clearBindings"
|
|
||||||
| "bind"
|
|
||||||
| "getHostInfo"
|
|
||||||
)]: Effects[K] extends Function ? Parameters<Effects[K]>[0] : never
|
|
||||||
}>({
|
}>({
|
||||||
executeAction: {} as ExecuteAction,
|
executeAction: {} as ExecuteAction,
|
||||||
createOverlayedImage: {} as CreateOverlayedImageParams,
|
createOverlayedImage: {} as CreateOverlayedImageParams,
|
||||||
@@ -57,7 +42,7 @@ describe("startosTypeValidation ", () => {
|
|||||||
exists: {} as ParamsPackageId,
|
exists: {} as ParamsPackageId,
|
||||||
getConfigured: undefined,
|
getConfigured: undefined,
|
||||||
stopped: {} as ParamsMaybePackageId,
|
stopped: {} as ParamsMaybePackageId,
|
||||||
running: {} as ParamsMaybePackageId,
|
running: {} as ParamsPackageId,
|
||||||
restart: undefined,
|
restart: undefined,
|
||||||
shutdown: undefined,
|
shutdown: undefined,
|
||||||
setConfigured: {} as SetConfigured,
|
setConfigured: {} as SetConfigured,
|
||||||
@@ -67,6 +52,20 @@ describe("startosTypeValidation ", () => {
|
|||||||
getSslCertificate: {} as GetSslCertificateParams,
|
getSslCertificate: {} as GetSslCertificateParams,
|
||||||
getSslKey: {} as GetSslKeyParams,
|
getSslKey: {} as GetSslKeyParams,
|
||||||
getServiceInterface: {} as GetServiceInterfaceParams,
|
getServiceInterface: {} as GetServiceInterfaceParams,
|
||||||
|
setDependencies: {} as SetDependenciesParams,
|
||||||
|
store: {} as never,
|
||||||
|
getSystemSmtp: {} as GetSystemSmtpParams,
|
||||||
|
getContainerIp: undefined,
|
||||||
|
getServicePortForward: {} as GetServicePortForwardParams,
|
||||||
|
clearServiceInterfaces: undefined,
|
||||||
|
exportServiceInterface: {} as ExportServiceInterfaceParams,
|
||||||
|
getPrimaryUrl: {} as GetPrimaryUrlParams,
|
||||||
|
listServiceInterfaces: {} as ListServiceInterfacesParams,
|
||||||
|
removeAddress: {} as RemoveAddressParams,
|
||||||
|
exportAction: {} as ExportActionParams,
|
||||||
|
removeAction: {} as RemoveActionParams,
|
||||||
|
reverseProxy: {} as ReverseProxyParams,
|
||||||
|
mount: {} as MountParams,
|
||||||
})
|
})
|
||||||
typeEquality<Parameters<Effects["executeAction"]>[0]>(
|
typeEquality<Parameters<Effects["executeAction"]>[0]>(
|
||||||
testInput as ExecuteAction,
|
testInput as ExecuteAction,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
|
import { MainEffects, StartSdk } from "../StartSdk"
|
||||||
import { Effects } from "../types"
|
import { Effects } from "../types"
|
||||||
import { createMainUtils } from "../util"
|
|
||||||
import { createUtils } from "../util/utils"
|
|
||||||
|
|
||||||
type Store = {
|
type Store = {
|
||||||
config: {
|
config: {
|
||||||
@@ -12,26 +11,31 @@ const todo = <A>(): A => {
|
|||||||
throw new Error("not implemented")
|
throw new Error("not implemented")
|
||||||
}
|
}
|
||||||
const noop = () => {}
|
const noop = () => {}
|
||||||
|
|
||||||
|
const sdk = StartSdk.of()
|
||||||
|
.withManifest({} as Manifest)
|
||||||
|
.withStore<Store>()
|
||||||
|
.build(true)
|
||||||
|
|
||||||
describe("Store", () => {
|
describe("Store", () => {
|
||||||
test("types", async () => {
|
test("types", async () => {
|
||||||
;async () => {
|
;async () => {
|
||||||
createUtils<Manifest, Store>(todo<Effects>()).store.setOwn("/config", {
|
sdk.store.setOwn(todo<Effects>(), "/config", {
|
||||||
someValue: "a",
|
someValue: "a",
|
||||||
})
|
})
|
||||||
createUtils<Manifest, Store>(todo<Effects>()).store.setOwn(
|
sdk.store.setOwn(todo<Effects>(), "/config/someValue", "b")
|
||||||
"/config/someValue",
|
sdk.store.setOwn(todo<Effects>(), "", {
|
||||||
"b",
|
|
||||||
)
|
|
||||||
createUtils<Manifest, Store>(todo<Effects>()).store.setOwn("", {
|
|
||||||
config: { someValue: "b" },
|
config: { someValue: "b" },
|
||||||
})
|
})
|
||||||
createUtils<Manifest, Store>(todo<Effects>()).store.setOwn(
|
sdk.store.setOwn(
|
||||||
|
todo<Effects>(),
|
||||||
"/config/someValue",
|
"/config/someValue",
|
||||||
|
|
||||||
// @ts-expect-error Type is wrong for the setting value
|
// @ts-expect-error Type is wrong for the setting value
|
||||||
5,
|
5,
|
||||||
)
|
)
|
||||||
createUtils(todo<Effects>()).store.setOwn(
|
sdk.store.setOwn(
|
||||||
|
todo<Effects>(),
|
||||||
// @ts-expect-error Path is wrong
|
// @ts-expect-error Path is wrong
|
||||||
"/config/someVae3lue",
|
"/config/someVae3lue",
|
||||||
"someValue",
|
"someValue",
|
||||||
@@ -52,49 +56,47 @@ describe("Store", () => {
|
|||||||
path: "/config/some2Value",
|
path: "/config/some2Value",
|
||||||
value: "a",
|
value: "a",
|
||||||
})
|
})
|
||||||
;(await createMainUtils<Manifest, Store>(todo<Effects>())
|
;(await sdk.store
|
||||||
.store.getOwn("/config/someValue")
|
.getOwn(todo<MainEffects>(), "/config/someValue")
|
||||||
.const()) satisfies string
|
.const()) satisfies string
|
||||||
;(await createMainUtils<Manifest, Store>(todo<Effects>())
|
;(await sdk.store
|
||||||
.store.getOwn("/config")
|
.getOwn(todo<MainEffects>(), "/config")
|
||||||
.const()) satisfies Store["config"]
|
.const()) satisfies Store["config"]
|
||||||
await createMainUtils(todo<Effects>())
|
await sdk.store // @ts-expect-error Path is wrong
|
||||||
// @ts-expect-error Path is wrong
|
.getOwn(todo<MainEffects>(), "/config/somdsfeValue")
|
||||||
.store.getOwn("/config/somdsfeValue")
|
|
||||||
.const()
|
.const()
|
||||||
/// ----------------- ERRORS -----------------
|
/// ----------------- ERRORS -----------------
|
||||||
|
|
||||||
createUtils<Manifest, Store>(todo<Effects>()).store.setOwn("", {
|
sdk.store.setOwn(todo<MainEffects>(), "", {
|
||||||
// @ts-expect-error Type is wrong for the setting value
|
// @ts-expect-error Type is wrong for the setting value
|
||||||
config: { someValue: "notInAOrB" },
|
config: { someValue: "notInAOrB" },
|
||||||
})
|
})
|
||||||
createUtils<Manifest, Store>(todo<Effects>()).store.setOwn(
|
sdk.store.setOwn(
|
||||||
|
todo<MainEffects>(),
|
||||||
"/config/someValue",
|
"/config/someValue",
|
||||||
// @ts-expect-error Type is wrong for the setting value
|
// @ts-expect-error Type is wrong for the setting value
|
||||||
"notInAOrB",
|
"notInAOrB",
|
||||||
)
|
)
|
||||||
;(await createUtils<Manifest, Store>(todo<Effects>())
|
;(await sdk.store
|
||||||
.store.getOwn("/config/someValue")
|
.getOwn(todo<Effects>(), "/config/someValue")
|
||||||
// @ts-expect-error Const should normally not be callable
|
// @ts-expect-error Const should normally not be callable
|
||||||
.const()) satisfies string
|
.const()) satisfies string
|
||||||
;(await createUtils<Manifest, Store>(todo<Effects>())
|
;(await sdk.store
|
||||||
.store.getOwn("/config")
|
.getOwn(todo<Effects>(), "/config")
|
||||||
// @ts-expect-error Const should normally not be callable
|
// @ts-expect-error Const should normally not be callable
|
||||||
.const()) satisfies Store["config"]
|
.const()) satisfies Store["config"]
|
||||||
await createUtils<Manifest, Store>(todo<Effects>())
|
await sdk.store // @ts-expect-error Path is wrong
|
||||||
// @ts-expect-error Path is wrong
|
.getOwn("/config/somdsfeValue")
|
||||||
.store.getOwn("/config/somdsfeValue")
|
|
||||||
// @ts-expect-error Const should normally not be callable
|
// @ts-expect-error Const should normally not be callable
|
||||||
.const()
|
.const()
|
||||||
|
|
||||||
///
|
///
|
||||||
;(await createUtils<Manifest, Store>(todo<Effects>())
|
;(await sdk.store
|
||||||
.store.getOwn("/config/someValue")
|
.getOwn(todo<MainEffects>(), "/config/someValue")
|
||||||
// @ts-expect-error satisfies type is wrong
|
// @ts-expect-error satisfies type is wrong
|
||||||
.const()) satisfies number
|
.const()) satisfies number
|
||||||
;(await createMainUtils(todo<Effects>())
|
;(await sdk.store // @ts-expect-error Path is wrong
|
||||||
// @ts-expect-error Path is wrong
|
.getOwn(todo<MainEffects>(), "/config/")
|
||||||
.store.getOwn("/config/")
|
|
||||||
.const()) satisfies Store["config"]
|
.const()) satisfies Store["config"]
|
||||||
;(await todo<Effects>().store.get<Store, "/config/someValue">({
|
;(await todo<Effects>().store.get<Store, "/config/someValue">({
|
||||||
path: "/config/someValue",
|
path: "/config/someValue",
|
||||||
|
|||||||
102
sdk/lib/types.ts
102
sdk/lib/types.ts
@@ -1,11 +1,11 @@
|
|||||||
export * as configTypes from "./config/configTypes"
|
export * as configTypes from "./config/configTypes"
|
||||||
import { AddSslOptions } from "../../core/startos/bindings/AddSslOptions"
|
import { AddSslOptions } from "../../core/startos/bindings/AddSslOptions"
|
||||||
|
import { MainEffects, ServiceInterfaceType, Signals } from "./StartSdk"
|
||||||
import { InputSpec } from "./config/configTypes"
|
import { InputSpec } from "./config/configTypes"
|
||||||
import { DependenciesReceipt } from "./config/setupConfig"
|
import { DependenciesReceipt } from "./config/setupConfig"
|
||||||
import { BindOptions, Scheme } from "./interfaces/Host"
|
import { BindOptions, Scheme } from "./interfaces/Host"
|
||||||
import { Daemons } from "./mainFn/Daemons"
|
import { Daemons } from "./mainFn/Daemons"
|
||||||
import { UrlString } from "./util/getServiceInterface"
|
import { UrlString } from "./util/getServiceInterface"
|
||||||
import { ServiceInterfaceType, Signals } from "./util/utils"
|
|
||||||
|
|
||||||
export type ExportedAction = (options: {
|
export type ExportedAction = (options: {
|
||||||
effects: Effects
|
effects: Effects
|
||||||
@@ -59,7 +59,7 @@ export namespace ExpectedExports {
|
|||||||
* package represents, like running a bitcoind in a bitcoind-wrapper.
|
* package represents, like running a bitcoind in a bitcoind-wrapper.
|
||||||
*/
|
*/
|
||||||
export type main = (options: {
|
export type main = (options: {
|
||||||
effects: Effects
|
effects: MainEffects
|
||||||
started(onTerm: () => PromiseLike<void>): PromiseLike<void>
|
started(onTerm: () => PromiseLike<void>): PromiseLike<void>
|
||||||
}) => Promise<Daemons<any, any>>
|
}) => Promise<Daemons<any, any>>
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ export type ActionMetadata = {
|
|||||||
/**
|
/**
|
||||||
* So the ordering of the actions is by alphabetical order of the group, then followed by the alphabetical of the actions
|
* So the ordering of the actions is by alphabetical order of the group, then followed by the alphabetical of the actions
|
||||||
*/
|
*/
|
||||||
group?: string
|
group: string | null
|
||||||
}
|
}
|
||||||
export declare const hostName: unique symbol
|
export declare const hostName: unique symbol
|
||||||
// asdflkjadsf.onion | 1.2.3.4
|
// asdflkjadsf.onion | 1.2.3.4
|
||||||
@@ -261,24 +261,47 @@ export type ExposeServicePaths<Store = never> = {
|
|||||||
paths: Store extends never ? string[] : ExposeAllServicePaths<Store>[]
|
paths: Store extends never ? string[] : ExposeAllServicePaths<Store>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExposeUiPaths<Store> = Array<{
|
export type ExposeUiPaths<Store> =
|
||||||
/** The path to the value in the Store. [JsonPath](https://jsonpath.com/) */
|
| {
|
||||||
path: ExposeAllUiPaths<Store>
|
type: "object"
|
||||||
/** A human readable title for the value */
|
value: { [k: string]: ExposeUiPaths<Store> }
|
||||||
title: string
|
}
|
||||||
/** A human readable description or explanation of the value */
|
| {
|
||||||
description?: string
|
type: "string"
|
||||||
/** (string/number only) Whether or not to mask the value, for example, when displaying a password */
|
/** The path to the value in the Store. [JsonPath](https://jsonpath.com/) */
|
||||||
masked?: boolean
|
path: ExposeAllUiPaths<Store>
|
||||||
/** (string/number only) Whether or not to include a button for copying the value to clipboard */
|
/** A human readable description or explanation of the value */
|
||||||
copyable?: boolean
|
description?: string
|
||||||
/** (string/number only) Whether or not to include a button for displaying the value as a QR code */
|
/** (string/number only) Whether or not to mask the value, for example, when displaying a password */
|
||||||
qr?: boolean
|
masked: boolean
|
||||||
}>
|
/** (string/number only) Whether or not to include a button for copying the value to clipboard */
|
||||||
|
copyable?: boolean
|
||||||
|
/** (string/number only) Whether or not to include a button for displaying the value as a QR code */
|
||||||
|
qr?: boolean
|
||||||
|
}
|
||||||
|
export type ExposeUiPathsAll =
|
||||||
|
| {
|
||||||
|
type: "object"
|
||||||
|
value: { [k: string]: ExposeUiPathsAll }
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "string"
|
||||||
|
/** The path to the value in the Store. [JsonPath](https://jsonpath.com/) */
|
||||||
|
path: string
|
||||||
|
/** A human readable description or explanation of the value */
|
||||||
|
description: string | null
|
||||||
|
/** (string/number only) Whether or not to mask the value, for example, when displaying a password */
|
||||||
|
masked: boolean
|
||||||
|
/** (string/number only) Whether or not to include a button for copying the value to clipboard */
|
||||||
|
copyable: boolean | null
|
||||||
|
/** (string/number only) Whether or not to include a button for displaying the value as a QR code */
|
||||||
|
qr: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
/** Used to reach out from the pure js runtime */
|
/** Used to reach out from the pure js runtime */
|
||||||
export type Effects = {
|
export type Effects = {
|
||||||
executeAction<Input>(opts: {
|
executeAction<Input>(opts: {
|
||||||
serviceId?: string
|
serviceId: string | null
|
||||||
input: Input
|
input: Input
|
||||||
}): Promise<unknown>
|
}): Promise<unknown>
|
||||||
|
|
||||||
@@ -286,10 +309,7 @@ export type Effects = {
|
|||||||
createOverlayedImage(options: { imageId: string }): Promise<[string, string]>
|
createOverlayedImage(options: { imageId: string }): Promise<[string, string]>
|
||||||
|
|
||||||
/** A low level api used by destroyOverlay + makeOverlay:destroy */
|
/** A low level api used by destroyOverlay + makeOverlay:destroy */
|
||||||
destroyOverlayedImage(options: {
|
destroyOverlayedImage(options: { guid: string }): Promise<void>
|
||||||
imageId: string
|
|
||||||
guid: string
|
|
||||||
}): Promise<void>
|
|
||||||
|
|
||||||
/** Removes all network bindings */
|
/** Removes all network bindings */
|
||||||
clearBindings(): Promise<void>
|
clearBindings(): Promise<void>
|
||||||
@@ -302,8 +322,7 @@ export type Effects = {
|
|||||||
scheme: Scheme
|
scheme: Scheme
|
||||||
preferredExternalPort: number
|
preferredExternalPort: number
|
||||||
addSsl: AddSslOptions | null
|
addSsl: AddSslOptions | null
|
||||||
secure: boolean
|
secure: { ssl: boolean } | null
|
||||||
ssl: boolean
|
|
||||||
}): Promise<void>
|
}): Promise<void>
|
||||||
/** Retrieves the current hostname(s) associated with a host id */
|
/** Retrieves the current hostname(s) associated with a host id */
|
||||||
// getHostInfo(options: {
|
// getHostInfo(options: {
|
||||||
@@ -362,10 +381,10 @@ export type Effects = {
|
|||||||
/**
|
/**
|
||||||
* Get the port address for another service
|
* Get the port address for another service
|
||||||
*/
|
*/
|
||||||
getServicePortForward(
|
getServicePortForward(options: {
|
||||||
internalPort: number,
|
internalPort: number
|
||||||
packageId?: string,
|
packageId: string | null
|
||||||
): Promise<number>
|
}): Promise<number>
|
||||||
|
|
||||||
/** Removes all network interfaces */
|
/** Removes all network interfaces */
|
||||||
clearServiceInterfaces(): Promise<void>
|
clearServiceInterfaces(): Promise<void>
|
||||||
@@ -376,16 +395,7 @@ export type Effects = {
|
|||||||
|
|
||||||
exposeForDependents(options: { paths: string[] }): Promise<void>
|
exposeForDependents(options: { paths: string[] }): Promise<void>
|
||||||
|
|
||||||
exposeUi<Store = never>(options: {
|
exposeUi(options: ExposeUiPathsAll): Promise<void>
|
||||||
paths: {
|
|
||||||
path: string
|
|
||||||
title: string
|
|
||||||
description?: string | undefined
|
|
||||||
masked?: boolean | undefined
|
|
||||||
copyable?: boolean | undefined
|
|
||||||
qr?: boolean | undefined
|
|
||||||
}[]
|
|
||||||
}): Promise<void>
|
|
||||||
/**
|
/**
|
||||||
* There are times that we want to see the addresses that where exported
|
* There are times that we want to see the addresses that where exported
|
||||||
* @param options.addressId If we want to filter the address id
|
* @param options.addressId If we want to filter the address id
|
||||||
@@ -467,7 +477,9 @@ export type Effects = {
|
|||||||
}): Promise<void>
|
}): Promise<void>
|
||||||
|
|
||||||
/** Set the dependencies of what the service needs, usually ran during the set config as a best practice */
|
/** Set the dependencies of what the service needs, usually ran during the set config as a best practice */
|
||||||
setDependencies(dependencies: Dependencies): Promise<DependenciesReceipt>
|
setDependencies(options: {
|
||||||
|
dependencies: Dependencies
|
||||||
|
}): Promise<DependenciesReceipt>
|
||||||
/** Exists could be useful during the runtime to know if some service exists, option dep */
|
/** Exists could be useful during the runtime to know if some service exists, option dep */
|
||||||
exists(options: { packageId: PackageId }): Promise<boolean>
|
exists(options: { packageId: PackageId }): Promise<boolean>
|
||||||
/** Exists could be useful during the runtime to know if some service is running, option dep */
|
/** Exists could be useful during the runtime to know if some service is running, option dep */
|
||||||
@@ -477,20 +489,20 @@ export type Effects = {
|
|||||||
reverseProxy(options: {
|
reverseProxy(options: {
|
||||||
bind: {
|
bind: {
|
||||||
/** Optional, default is 0.0.0.0 */
|
/** Optional, default is 0.0.0.0 */
|
||||||
ip?: string
|
ip: string | null
|
||||||
port: number
|
port: number
|
||||||
ssl: boolean
|
ssl: boolean
|
||||||
}
|
}
|
||||||
dst: {
|
dst: {
|
||||||
/** Optional: default is 127.0.0.1 */
|
/** Optional: default is 127.0.0.1 */
|
||||||
ip?: string // optional, default 127.0.0.1
|
ip: string | null // optional, default 127.0.0.1
|
||||||
port: number
|
port: number
|
||||||
ssl: boolean
|
ssl: boolean
|
||||||
}
|
}
|
||||||
http?: {
|
http: {
|
||||||
// optional, will do TCP layer proxy only if not present
|
// optional, will do TCP layer proxy only if not present
|
||||||
headers?: (headers: Record<string, string>) => Record<string, string>
|
headers: Record<string, string> | null
|
||||||
}
|
} | null
|
||||||
}): Promise<{ stop(): Promise<void> }>
|
}): Promise<{ stop(): Promise<void> }>
|
||||||
restart(): void
|
restart(): void
|
||||||
shutdown(): void
|
shutdown(): void
|
||||||
@@ -585,7 +597,7 @@ export type KnownError =
|
|||||||
export type Dependency = {
|
export type Dependency = {
|
||||||
id: PackageId
|
id: PackageId
|
||||||
kind: DependencyKind
|
kind: DependencyKind
|
||||||
}
|
} & ({ kind: "exists" } | { kind: "running"; healthChecks: string[] })
|
||||||
export type Dependencies = Array<Dependency>
|
export type Dependencies = Array<Dependency>
|
||||||
|
|
||||||
export type DeepPartial<T> = T extends {}
|
export type DeepPartial<T> = T extends {}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export class Overlay {
|
|||||||
async destroy() {
|
async destroy() {
|
||||||
const imageId = this.imageId
|
const imageId = this.imageId
|
||||||
const guid = this.guid
|
const guid = this.guid
|
||||||
await this.effects.destroyOverlayedImage({ imageId, guid })
|
await this.effects.destroyOverlayedImage({ guid })
|
||||||
}
|
}
|
||||||
|
|
||||||
async exec(
|
async exec(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ServiceInterfaceType } from "../StartSdk"
|
||||||
import {
|
import {
|
||||||
AddressInfo,
|
AddressInfo,
|
||||||
Effects,
|
Effects,
|
||||||
@@ -5,7 +6,6 @@ import {
|
|||||||
Hostname,
|
Hostname,
|
||||||
HostnameInfo,
|
HostnameInfo,
|
||||||
} from "../types"
|
} from "../types"
|
||||||
import { ServiceInterfaceType } from "./utils"
|
|
||||||
|
|
||||||
export type UrlString = string
|
export type UrlString = string
|
||||||
export type HostId = string
|
export type HostId = string
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import "./deepEqual"
|
|||||||
import "./deepMerge"
|
import "./deepMerge"
|
||||||
import "./Overlay"
|
import "./Overlay"
|
||||||
import "./once"
|
import "./once"
|
||||||
import * as utils from "./utils"
|
|
||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
import { SDKManifest } from "../manifest/ManifestTypes"
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@@ -23,11 +22,6 @@ export const isKnownError = (e: unknown): e is T.KnownError =>
|
|||||||
|
|
||||||
declare const affine: unique symbol
|
declare const affine: unique symbol
|
||||||
|
|
||||||
export const createUtils = utils.createUtils
|
|
||||||
export const createMainUtils = <Manifest extends SDKManifest, Store>(
|
|
||||||
effects: T.Effects,
|
|
||||||
) => createUtils<Manifest, Store, {}>(effects)
|
|
||||||
|
|
||||||
type NeverPossible = { [affine]: string }
|
type NeverPossible = { [affine]: string }
|
||||||
export type NoAny<A> = NeverPossible extends A
|
export type NoAny<A> = NeverPossible extends A
|
||||||
? keyof NeverPossible extends keyof A
|
? keyof NeverPossible extends keyof A
|
||||||
|
|||||||
@@ -1,300 +0,0 @@
|
|||||||
import nullIfEmpty from "./nullIfEmpty"
|
|
||||||
import {
|
|
||||||
CheckResult,
|
|
||||||
checkPortListening,
|
|
||||||
checkWebUrl,
|
|
||||||
} from "../health/checkFns"
|
|
||||||
import {
|
|
||||||
DaemonReturned,
|
|
||||||
Effects,
|
|
||||||
EnsureStorePath,
|
|
||||||
ExtractStore,
|
|
||||||
ServiceInterfaceId,
|
|
||||||
PackageId,
|
|
||||||
ValidIfNoStupidEscape,
|
|
||||||
} from "../types"
|
|
||||||
import { GetSystemSmtp } from "./GetSystemSmtp"
|
|
||||||
import { GetStore, getStore } from "../store/getStore"
|
|
||||||
import { MultiHost, Scheme, SingleHost, StaticHost } from "../interfaces/Host"
|
|
||||||
import { ServiceInterfaceBuilder } from "../interfaces/ServiceInterfaceBuilder"
|
|
||||||
import { GetServiceInterface, getServiceInterface } from "./getServiceInterface"
|
|
||||||
import {
|
|
||||||
GetServiceInterfaces,
|
|
||||||
getServiceInterfaces,
|
|
||||||
} from "./getServiceInterfaces"
|
|
||||||
import * as CP from "node:child_process"
|
|
||||||
import { promisify } from "node:util"
|
|
||||||
import { splitCommand } from "./splitCommand"
|
|
||||||
import { SDKManifest } from "../manifest/ManifestTypes"
|
|
||||||
import { MountOptions, Overlay, CommandOptions } from "./Overlay"
|
|
||||||
export type Signals = NodeJS.Signals
|
|
||||||
|
|
||||||
export const SIGTERM: Signals = "SIGTERM"
|
|
||||||
export const SIGKILL: Signals = "SIGTERM"
|
|
||||||
export const NO_TIMEOUT = -1
|
|
||||||
|
|
||||||
const childProcess = {
|
|
||||||
exec: promisify(CP.exec),
|
|
||||||
execFile: promisify(CP.execFile),
|
|
||||||
}
|
|
||||||
const cp = childProcess
|
|
||||||
|
|
||||||
export type ServiceInterfaceType = "ui" | "p2p" | "api"
|
|
||||||
|
|
||||||
export type Utils<
|
|
||||||
Manifest extends SDKManifest,
|
|
||||||
Store,
|
|
||||||
WrapperOverWrite = { const: never },
|
|
||||||
> = {
|
|
||||||
checkPortListening(
|
|
||||||
port: number,
|
|
||||||
options: {
|
|
||||||
errorMessage: string
|
|
||||||
successMessage: string
|
|
||||||
timeoutMessage?: string
|
|
||||||
timeout?: number
|
|
||||||
},
|
|
||||||
): Promise<CheckResult>
|
|
||||||
checkWebUrl(
|
|
||||||
url: string,
|
|
||||||
options?: {
|
|
||||||
timeout?: number
|
|
||||||
successMessage?: string
|
|
||||||
errorMessage?: string
|
|
||||||
},
|
|
||||||
): Promise<CheckResult>
|
|
||||||
childProcess: typeof childProcess
|
|
||||||
createInterface: (options: {
|
|
||||||
name: string
|
|
||||||
id: string
|
|
||||||
description: string
|
|
||||||
hasPrimary: boolean
|
|
||||||
disabled: boolean
|
|
||||||
type: ServiceInterfaceType
|
|
||||||
username: null | string
|
|
||||||
path: string
|
|
||||||
search: Record<string, string>
|
|
||||||
schemeOverride: { ssl: Scheme; noSsl: Scheme } | null
|
|
||||||
masked: boolean
|
|
||||||
}) => ServiceInterfaceBuilder
|
|
||||||
getSystemSmtp: () => GetSystemSmtp & WrapperOverWrite
|
|
||||||
host: {
|
|
||||||
static: (id: string) => StaticHost
|
|
||||||
single: (id: string) => SingleHost
|
|
||||||
multi: (id: string) => MultiHost
|
|
||||||
}
|
|
||||||
serviceInterface: {
|
|
||||||
getOwn: (id: ServiceInterfaceId) => GetServiceInterface & WrapperOverWrite
|
|
||||||
get: (opts: {
|
|
||||||
id: ServiceInterfaceId
|
|
||||||
packageId: PackageId
|
|
||||||
}) => GetServiceInterface & WrapperOverWrite
|
|
||||||
getAllOwn: () => GetServiceInterfaces & WrapperOverWrite
|
|
||||||
getAll: (opts: {
|
|
||||||
packageId: PackageId
|
|
||||||
}) => GetServiceInterfaces & WrapperOverWrite
|
|
||||||
}
|
|
||||||
nullIfEmpty: typeof nullIfEmpty
|
|
||||||
runCommand: <A extends string>(
|
|
||||||
imageId: Manifest["images"][number],
|
|
||||||
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
|
||||||
options: CommandOptions & {
|
|
||||||
mounts?: { path: string; options: MountOptions }[]
|
|
||||||
},
|
|
||||||
) => Promise<{ stdout: string | Buffer; stderr: string | Buffer }>
|
|
||||||
runDaemon: <A extends string>(
|
|
||||||
imageId: Manifest["images"][number],
|
|
||||||
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
|
||||||
options: CommandOptions & {
|
|
||||||
mounts?: { path: string; options: MountOptions }[]
|
|
||||||
overlay?: Overlay
|
|
||||||
},
|
|
||||||
) => Promise<DaemonReturned>
|
|
||||||
store: {
|
|
||||||
get: <Path extends string>(
|
|
||||||
packageId: string,
|
|
||||||
path: EnsureStorePath<Store, Path>,
|
|
||||||
) => GetStore<Store, Path> & WrapperOverWrite
|
|
||||||
getOwn: <Path extends string>(
|
|
||||||
path: EnsureStorePath<Store, Path>,
|
|
||||||
) => GetStore<Store, Path> & WrapperOverWrite
|
|
||||||
setOwn: <Path extends string | never>(
|
|
||||||
path: EnsureStorePath<Store, Path>,
|
|
||||||
value: ExtractStore<Store, Path>,
|
|
||||||
) => Promise<void>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const createUtils = <
|
|
||||||
Manifest extends SDKManifest,
|
|
||||||
Store = never,
|
|
||||||
WrapperOverWrite = { const: never },
|
|
||||||
>(
|
|
||||||
effects: Effects,
|
|
||||||
): Utils<Manifest, Store, WrapperOverWrite> => {
|
|
||||||
return {
|
|
||||||
createInterface: (options: {
|
|
||||||
name: string
|
|
||||||
id: string
|
|
||||||
description: string
|
|
||||||
hasPrimary: boolean
|
|
||||||
disabled: boolean
|
|
||||||
type: ServiceInterfaceType
|
|
||||||
username: null | string
|
|
||||||
path: string
|
|
||||||
search: Record<string, string>
|
|
||||||
schemeOverride: { ssl: Scheme; noSsl: Scheme } | null
|
|
||||||
masked: boolean
|
|
||||||
}) => new ServiceInterfaceBuilder({ ...options, effects }),
|
|
||||||
childProcess,
|
|
||||||
getSystemSmtp: () =>
|
|
||||||
new GetSystemSmtp(effects) as GetSystemSmtp & WrapperOverWrite,
|
|
||||||
|
|
||||||
host: {
|
|
||||||
static: (id: string) => new StaticHost({ id, effects }),
|
|
||||||
single: (id: string) => new SingleHost({ id, effects }),
|
|
||||||
multi: (id: string) => new MultiHost({ id, effects }),
|
|
||||||
},
|
|
||||||
nullIfEmpty,
|
|
||||||
|
|
||||||
serviceInterface: {
|
|
||||||
getOwn: (id: ServiceInterfaceId) =>
|
|
||||||
getServiceInterface(effects, {
|
|
||||||
id,
|
|
||||||
packageId: null,
|
|
||||||
}) as GetServiceInterface & WrapperOverWrite,
|
|
||||||
get: (opts: { id: ServiceInterfaceId; packageId: PackageId }) =>
|
|
||||||
getServiceInterface(effects, opts) as GetServiceInterface &
|
|
||||||
WrapperOverWrite,
|
|
||||||
getAllOwn: () =>
|
|
||||||
getServiceInterfaces(effects, {
|
|
||||||
packageId: null,
|
|
||||||
}) as GetServiceInterfaces & WrapperOverWrite,
|
|
||||||
getAll: (opts: { packageId: PackageId }) =>
|
|
||||||
getServiceInterfaces(effects, opts) as GetServiceInterfaces &
|
|
||||||
WrapperOverWrite,
|
|
||||||
},
|
|
||||||
store: {
|
|
||||||
get: <Path extends string = never>(
|
|
||||||
packageId: string,
|
|
||||||
path: EnsureStorePath<Store, Path>,
|
|
||||||
) =>
|
|
||||||
getStore<Store, Path>(effects, path as any, {
|
|
||||||
packageId,
|
|
||||||
}) as any,
|
|
||||||
getOwn: <Path extends string>(path: EnsureStorePath<Store, Path>) =>
|
|
||||||
getStore<Store, Path>(effects, path as any) as any,
|
|
||||||
setOwn: <Path extends string | never>(
|
|
||||||
path: EnsureStorePath<Store, Path>,
|
|
||||||
value: ExtractStore<Store, Path>,
|
|
||||||
) => effects.store.set<Store, Path>({ value, path: path as any }),
|
|
||||||
},
|
|
||||||
|
|
||||||
runCommand: async <A extends string>(
|
|
||||||
imageId: Manifest["images"][number],
|
|
||||||
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
|
||||||
options: CommandOptions & {
|
|
||||||
mounts?: { path: string; options: MountOptions }[]
|
|
||||||
},
|
|
||||||
): Promise<{ stdout: string | Buffer; stderr: string | Buffer }> => {
|
|
||||||
const commands = splitCommand(command)
|
|
||||||
const overlay = await Overlay.of(effects, imageId)
|
|
||||||
try {
|
|
||||||
for (let mount of options.mounts || []) {
|
|
||||||
await overlay.mount(mount.options, mount.path)
|
|
||||||
}
|
|
||||||
return await overlay.exec(commands)
|
|
||||||
} finally {
|
|
||||||
await overlay.destroy()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
runDaemon: async <A extends string>(
|
|
||||||
imageId: Manifest["images"][number],
|
|
||||||
command: ValidIfNoStupidEscape<A> | [string, ...string[]],
|
|
||||||
options: CommandOptions & {
|
|
||||||
mounts?: { path: string; options: MountOptions }[]
|
|
||||||
overlay?: Overlay
|
|
||||||
},
|
|
||||||
): Promise<DaemonReturned> => {
|
|
||||||
const commands = splitCommand(command)
|
|
||||||
const overlay = options.overlay || (await Overlay.of(effects, imageId))
|
|
||||||
for (let mount of options.mounts || []) {
|
|
||||||
await overlay.mount(mount.options, mount.path)
|
|
||||||
}
|
|
||||||
const childProcess = await overlay.spawn(commands, {
|
|
||||||
env: options.env,
|
|
||||||
})
|
|
||||||
const answer = new Promise<null>((resolve, reject) => {
|
|
||||||
childProcess.stdout.on("data", (data: any) => {
|
|
||||||
console.log(data.toString())
|
|
||||||
})
|
|
||||||
childProcess.stderr.on("data", (data: any) => {
|
|
||||||
console.error(data.toString())
|
|
||||||
})
|
|
||||||
|
|
||||||
childProcess.on("exit", (code: any) => {
|
|
||||||
if (code === 0) {
|
|
||||||
return resolve(null)
|
|
||||||
}
|
|
||||||
return reject(new Error(`${commands[0]} exited with code ${code}`))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const pid = childProcess.pid
|
|
||||||
return {
|
|
||||||
async wait() {
|
|
||||||
const pids = pid ? await psTree(pid, overlay) : []
|
|
||||||
try {
|
|
||||||
return await answer
|
|
||||||
} finally {
|
|
||||||
for (const process of pids) {
|
|
||||||
cp.execFile("kill", [`-9`, String(process)]).catch((_) => {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async term({ signal = SIGTERM, timeout = NO_TIMEOUT } = {}) {
|
|
||||||
const pids = pid ? await psTree(pid, overlay) : []
|
|
||||||
try {
|
|
||||||
childProcess.kill(signal)
|
|
||||||
|
|
||||||
if (timeout > NO_TIMEOUT) {
|
|
||||||
const didTimeout = await Promise.race([
|
|
||||||
new Promise((resolve) => setTimeout(resolve, timeout)).then(
|
|
||||||
() => true,
|
|
||||||
),
|
|
||||||
answer.then(() => false),
|
|
||||||
])
|
|
||||||
if (didTimeout) {
|
|
||||||
childProcess.kill(SIGKILL)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await answer
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
await overlay.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (const process of pids) {
|
|
||||||
await cp.execFile("kill", [`-${signal}`, String(process)])
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
for (const process of pids) {
|
|
||||||
cp.execFile("kill", [`-9`, String(process)]).catch((_) => {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
checkPortListening: checkPortListening.bind(null, effects),
|
|
||||||
checkWebUrl: checkWebUrl.bind(null, effects),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function noop(): void {}
|
|
||||||
|
|
||||||
async function psTree(pid: number, overlay: Overlay): Promise<number[]> {
|
|
||||||
const { stdout } = await childProcess.exec(`pstree -p ${pid}`)
|
|
||||||
const regex: RegExp = /\((\d+)\)/g
|
|
||||||
return [...stdout.toString().matchAll(regex)].map(([_all, pid]) =>
|
|
||||||
parseInt(pid),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -2,15 +2,15 @@
|
|||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-rev0.lib0.rc8.beta10",
|
"version": "0.4.0-rev0.lib0.rc8.beta10",
|
||||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||||
"main": "./cjs/lib/index.js",
|
"main": "./cjs/sdk/lib/index.js",
|
||||||
"types": "./cjs/lib/index.d.ts",
|
"types": "./cjs/sdk/lib/index.d.ts",
|
||||||
"module": "./mjs/lib/index.js",
|
"module": "./mjs/sdk/lib/index.js",
|
||||||
"sideEffects": true,
|
"sideEffects": true,
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"import": "./mjs/lib/index.js",
|
"import": "./mjs/sdk/lib/index.js",
|
||||||
"require": "./cjs/lib/index.js",
|
"require": "./cjs/sdk/lib/index.js",
|
||||||
"types": "./cjs/lib/index.d.ts"
|
"types": "./cjs/sdk/lib/index.d.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typesVersion": {
|
"typesVersion": {
|
||||||
@@ -56,4 +56,4 @@
|
|||||||
"tsx": "^4.7.1",
|
"tsx": "^4.7.1",
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ const ICONS = [
|
|||||||
'file-tray-stacked-outline',
|
'file-tray-stacked-outline',
|
||||||
'finger-print-outline',
|
'finger-print-outline',
|
||||||
'flash-outline',
|
'flash-outline',
|
||||||
'flask-outline',
|
|
||||||
'flash-off-outline',
|
|
||||||
'folder-open-outline',
|
'folder-open-outline',
|
||||||
'globe-outline',
|
'globe-outline',
|
||||||
'grid-outline',
|
'grid-outline',
|
||||||
@@ -70,6 +68,7 @@ const ICONS = [
|
|||||||
'receipt-outline',
|
'receipt-outline',
|
||||||
'refresh',
|
'refresh',
|
||||||
'reload',
|
'reload',
|
||||||
|
'reload-circle-outline',
|
||||||
'remove',
|
'remove',
|
||||||
'remove-circle-outline',
|
'remove-circle-outline',
|
||||||
'remove-outline',
|
'remove-outline',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<ion-content class="ion-padding-top with-widgets">
|
<ion-content class="ion-padding-top with-widgets">
|
||||||
<ion-item-group *ngIf="serviceInterfaces$ | async as serviceInterfaces">
|
<ion-item-group *ngIf="serviceInterfaces$ | async as serviceInterfaces">
|
||||||
<ng-container *ngIf="serviceInterfaces.ui.length">
|
<ng-container *ngIf="serviceInterfaces.ui.length">
|
||||||
<ion-item-divider>User Interfaces (UI)</ion-item-divider>
|
<ion-item-divider>User Interfaces</ion-item-divider>
|
||||||
<app-interfaces-item
|
<app-interfaces-item
|
||||||
*ngFor="let ui of serviceInterfaces.ui"
|
*ngFor="let ui of serviceInterfaces.ui"
|
||||||
[iFace]="ui"
|
[iFace]="ui"
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="serviceInterfaces.api.length">
|
<ng-container *ngIf="serviceInterfaces.api.length">
|
||||||
<ion-item-divider>Application Program Interfaces (API)</ion-item-divider>
|
<ion-item-divider>Application Program Interfaces</ion-item-divider>
|
||||||
<app-interfaces-item
|
<app-interfaces-item
|
||||||
*ngFor="let api of serviceInterfaces.api"
|
*ngFor="let api of serviceInterfaces.api"
|
||||||
[iFace]="api"
|
[iFace]="api"
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="serviceInterfaces.p2p.length">
|
<ng-container *ngIf="serviceInterfaces.p2p.length">
|
||||||
<ion-item-divider>Peer-To-Peer Interfaces (P2P)</ion-item-divider>
|
<ion-item-divider>Peer-To-Peer Interfaces</ion-item-divider>
|
||||||
<app-interfaces-item
|
<app-interfaces-item
|
||||||
*ngFor="let p2p of serviceInterfaces.p2p"
|
*ngFor="let p2p of serviceInterfaces.p2p"
|
||||||
[iFace]="p2p"
|
[iFace]="p2p"
|
||||||
|
|||||||
@@ -116,49 +116,54 @@ function getAddresses(
|
|||||||
? [host.hostname]
|
? [host.hostname]
|
||||||
: []
|
: []
|
||||||
|
|
||||||
return hostnames
|
const addresses: MappedAddress[] = []
|
||||||
.map(h => {
|
|
||||||
const addresses: MappedAddress[] = []
|
|
||||||
|
|
||||||
let name = ''
|
hostnames.forEach(h => {
|
||||||
let hostname = ''
|
let name = ''
|
||||||
|
let hostname = ''
|
||||||
|
|
||||||
if (h.kind === 'onion') {
|
if (h.kind === 'onion') {
|
||||||
name = 'Tor'
|
name = 'Tor'
|
||||||
hostname = h.hostname.value
|
hostname = h.hostname.value
|
||||||
|
} else {
|
||||||
|
const hostnameKind = h.hostname.kind
|
||||||
|
|
||||||
|
if (hostnameKind === 'domain') {
|
||||||
|
name = 'Domain'
|
||||||
|
hostname = `${h.hostname.subdomain}.${h.hostname.domain}`
|
||||||
} else {
|
} else {
|
||||||
name = h.hostname.kind
|
name =
|
||||||
hostname =
|
hostnameKind === 'local'
|
||||||
h.hostname.kind === 'domain'
|
? 'Local'
|
||||||
? `${h.hostname.subdomain}.${h.hostname.domain}`
|
: `${h.networkInterfaceId} (${hostnameKind})`
|
||||||
: h.hostname.value
|
hostname = h.hostname.value
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (h.hostname.sslPort) {
|
if (h.hostname.sslPort) {
|
||||||
const port = h.hostname.sslPort === 443 ? '' : `:${h.hostname.sslPort}`
|
const port = h.hostname.sslPort === 443 ? '' : `:${h.hostname.sslPort}`
|
||||||
const scheme = addressInfo.bindOptions.addSsl?.scheme
|
const scheme = addressInfo.bindOptions.addSsl?.scheme
|
||||||
? `${addressInfo.bindOptions.addSsl.scheme}://`
|
? `${addressInfo.bindOptions.addSsl.scheme}://`
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
addresses.push({
|
addresses.push({
|
||||||
name,
|
name: name === 'Tor' ? 'Tor (HTTPS)' : name,
|
||||||
url: `${scheme}${username}${hostname}${port}${suffix}`,
|
url: `${scheme}${username}${hostname}${port}${suffix}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (h.hostname.port) {
|
if (h.hostname.port) {
|
||||||
const port = h.hostname.port === 80 ? '' : `:${h.hostname.port}`
|
const port = h.hostname.port === 80 ? '' : `:${h.hostname.port}`
|
||||||
const scheme = addressInfo.bindOptions.scheme
|
const scheme = addressInfo.bindOptions.scheme
|
||||||
? `${addressInfo.bindOptions.scheme}://`
|
? `${addressInfo.bindOptions.scheme}://`
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
addresses.push({
|
addresses.push({
|
||||||
name,
|
name: name === 'Tor' ? 'Tor (HTTP)' : name,
|
||||||
url: `${scheme}${username}${hostname}${port}${suffix}`,
|
url: `${scheme}${username}${hostname}${port}${suffix}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return addresses
|
return addresses
|
||||||
})
|
|
||||||
.flat()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { Routes, RouterModule } from '@angular/router'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { ExperimentalFeaturesPage } from './experimental-features.page'
|
|
||||||
import { EmverPipesModule } from '@start9labs/shared'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: ExperimentalFeaturesPage,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
EmverPipesModule,
|
|
||||||
],
|
|
||||||
declarations: [ExperimentalFeaturesPage],
|
|
||||||
})
|
|
||||||
export class ExperimentalFeaturesPageModule {}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button defaultHref="system"></ion-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
<ion-title>Experimental Features</ion-title>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content class="with-widgets">
|
|
||||||
<ion-item-group *ngIf="server$ | async as server">
|
|
||||||
<ion-item button (click)="presentAlertResetTor()">
|
|
||||||
<ion-icon slot="start" name="reload"></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<h2>Reset Tor</h2>
|
|
||||||
<p>
|
|
||||||
Resetting the Tor daemon on your server may resolve Tor connectivity
|
|
||||||
issues.
|
|
||||||
</p>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item button (click)="presentAlertZram(server.zram)">
|
|
||||||
<ion-icon
|
|
||||||
slot="start"
|
|
||||||
[name]="server.zram ? 'flash-off-outline' : 'flash-outline'"
|
|
||||||
></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<h2>{{ server.zram ? 'Disable' : 'Enable' }} zram</h2>
|
|
||||||
<p>
|
|
||||||
Zram creates compressed swap in memory, resulting in faster I/O for
|
|
||||||
low RAM devices
|
|
||||||
</p>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</ion-item-group>
|
|
||||||
</ion-content>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user