mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
Bugfix/ssl proxy to ssl (#2956)
* fix registry rm command * fix bind with addSsl on ssl proto * fix bind with addSsl on ssl proto * Add pre-release version migrations * fix os build * add mime to package deps * update lockfile * more ssl fixes * add waitFor * improve restart lockup * beta.26 * fix dependency health check logic * handle missing health check * fix port forwards --------- Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
12
Makefile
12
Makefile
@@ -222,7 +222,11 @@ upload-ota: results/$(BASENAME).squashfs
|
|||||||
container-runtime/debian.$(ARCH).squashfs: ./container-runtime/download-base-image.sh
|
container-runtime/debian.$(ARCH).squashfs: ./container-runtime/download-base-image.sh
|
||||||
ARCH=$(ARCH) ./container-runtime/download-base-image.sh
|
ARCH=$(ARCH) ./container-runtime/download-base-image.sh
|
||||||
|
|
||||||
container-runtime/node_modules/.package-lock.json: container-runtime/package.json container-runtime/package-lock.json sdk/dist/package.json
|
container-runtime/package-lock.json: sdk/dist/package.json
|
||||||
|
npm --prefix container-runtime i
|
||||||
|
touch container-runtime/package-lock.json
|
||||||
|
|
||||||
|
container-runtime/node_modules/.package-lock.json: container-runtime/package-lock.json
|
||||||
npm --prefix container-runtime ci
|
npm --prefix container-runtime ci
|
||||||
touch container-runtime/node_modules/.package-lock.json
|
touch container-runtime/node_modules/.package-lock.json
|
||||||
|
|
||||||
@@ -277,7 +281,11 @@ core/target/$(ARCH)-unknown-linux-musl/release/containerbox: $(CORE_SRC) $(ENVIR
|
|||||||
ARCH=$(ARCH) ./core/build-containerbox.sh
|
ARCH=$(ARCH) ./core/build-containerbox.sh
|
||||||
touch core/target/$(ARCH)-unknown-linux-musl/release/containerbox
|
touch core/target/$(ARCH)-unknown-linux-musl/release/containerbox
|
||||||
|
|
||||||
web/node_modules/.package-lock.json: web/package.json sdk/baseDist/package.json
|
web/package-lock.json: web/package.json sdk/baseDist/package.json
|
||||||
|
npm --prefix web i
|
||||||
|
touch web/package-lock.json
|
||||||
|
|
||||||
|
web/node_modules/.package-lock.json: web/package-lock.json
|
||||||
npm --prefix web ci
|
npm --prefix web ci
|
||||||
touch web/node_modules/.package-lock.json
|
touch web/node_modules/.package-lock.json
|
||||||
|
|
||||||
|
|||||||
34
build/lib/scripts/forward-port
Normal file → Executable file
34
build/lib/scripts/forward-port
Normal file → Executable file
@@ -1,18 +1,26 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
iptables -F
|
if [ -z "$iiface" ] || [ -z "$oiface" ] || [ -z "$sip" ] || [ -z "$dip" ] || [ -z "$sport" ] || [ -z "$dport" ]; then
|
||||||
iptables -t nat -F
|
>&2 echo 'missing required env var'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
iptables -t nat -A POSTROUTING -o $iiface -j MASQUERADE
|
kind="-A"
|
||||||
iptables -t nat -A PREROUTING -i $iiface -p tcp --dport $sport -j DNAT --to-destination $dip:$dport
|
|
||||||
iptables -t nat -A PREROUTING -i $iiface -p udp --dport $sport -j DNAT --to-destination $dip:$dport
|
if [ "$UNDO" = 1 ]; then
|
||||||
iptables -t nat -A PREROUTING -i $oiface -s 10.0.3.0/24 -d $sip -p tcp --dport $sport -j DNAT --to-destination $dip:$dport
|
kind="-D"
|
||||||
iptables -t nat -A PREROUTING -i $oiface -s 10.0.3.0/24 -d $sip -p udp --dport $sport -j DNAT --to-destination $dip:$dport
|
fi
|
||||||
iptables -t nat -A POSTROUTING -o $oiface -s 10.0.3.0/24 -d $dip/32 -p tcp --dport $dport -j SNAT --to-source $sip:$sport
|
|
||||||
iptables -t nat -A POSTROUTING -o $oiface -s 10.0.3.0/24 -d $dip/32 -p udp --dport $dport -j SNAT --to-source $sip:$sport
|
iptables -t nat "$kind" POSTROUTING -o $iiface -j MASQUERADE
|
||||||
|
iptables -t nat "$kind" PREROUTING -i $iiface -p tcp --dport $sport -j DNAT --to-destination $dip:$dport
|
||||||
|
iptables -t nat "$kind" PREROUTING -i $iiface -p udp --dport $sport -j DNAT --to-destination $dip:$dport
|
||||||
|
iptables -t nat "$kind" PREROUTING -i $oiface -s $dip/24 -d $sip -p tcp --dport $sport -j DNAT --to-destination $dip:$dport
|
||||||
|
iptables -t nat "$kind" PREROUTING -i $oiface -s $dip/24 -d $sip -p udp --dport $sport -j DNAT --to-destination $dip:$dport
|
||||||
|
iptables -t nat "$kind" POSTROUTING -o $oiface -s $dip/24 -d $dip/32 -p tcp --dport $dport -j SNAT --to-source $sip:$sport
|
||||||
|
iptables -t nat "$kind" POSTROUTING -o $oiface -s $dip/24 -d $dip/32 -p udp --dport $dport -j SNAT --to-source $sip:$sport
|
||||||
|
|
||||||
|
|
||||||
iptables -t nat -A PREROUTING -i $iiface -s $sip/32 -d $sip -p tcp --dport $sport -j DNAT --to-destination $dip:$dport
|
iptables -t nat "$kind" PREROUTING -i $iiface -s $sip/32 -d $sip -p tcp --dport $sport -j DNAT --to-destination $dip:$dport
|
||||||
iptables -t nat -A PREROUTING -i $iiface -s $sip/32 -d $sip -p udp --dport $sport -j DNAT --to-destination $dip:$dport
|
iptables -t nat "$kind" PREROUTING -i $iiface -s $sip/32 -d $sip -p udp --dport $sport -j DNAT --to-destination $dip:$dport
|
||||||
iptables -t nat -A POSTROUTING -o $oiface -s $sip/32 -d $dip/32 -p tcp --dport $dport -j SNAT --to-source $sip:$sport
|
iptables -t nat "$kind" POSTROUTING -o $oiface -s $sip/32 -d $dip/32 -p tcp --dport $dport -j SNAT --to-source $sip:$sport
|
||||||
iptables -t nat -A POSTROUTING -o $oiface -s $sip/32 -d $dip/32 -p udp --dport $dport -j SNAT --to-source $sip:$sport
|
iptables -t nat "$kind" POSTROUTING -o $oiface -s $sip/32 -d $dip/32 -p udp --dport $dport -j SNAT --to-source $sip:$sport
|
||||||
30
container-runtime/package-lock.json
generated
30
container-runtime/package-lock.json
generated
@@ -17,6 +17,7 @@
|
|||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"jsonpath": "^1.1.1",
|
"jsonpath": "^1.1.1",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
|
"mime": "^4.0.7",
|
||||||
"node-fetch": "^3.1.0",
|
"node-fetch": "^3.1.0",
|
||||||
"ts-matches": "^6.3.2",
|
"ts-matches": "^6.3.2",
|
||||||
"tslib": "^2.5.3",
|
"tslib": "^2.5.3",
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
},
|
},
|
||||||
"../sdk/dist": {
|
"../sdk/dist": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.25",
|
"version": "0.4.0-beta.26",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
@@ -47,7 +48,7 @@
|
|||||||
"deep-equality-data-structures": "^2.0.0",
|
"deep-equality-data-structures": "^2.0.0",
|
||||||
"ini": "^5.0.0",
|
"ini": "^5.0.0",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"mime-types": "^3.0.1",
|
"mime": "^4.0.7",
|
||||||
"ts-matches": "^6.3.2",
|
"ts-matches": "^6.3.2",
|
||||||
"yaml": "^2.7.1"
|
"yaml": "^2.7.1"
|
||||||
},
|
},
|
||||||
@@ -4975,15 +4976,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mime": {
|
"node_modules/mime": {
|
||||||
"version": "1.6.0",
|
"version": "4.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz",
|
||||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
"integrity": "sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"mime": "cli.js"
|
"mime": "bin/cli.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mime-db": {
|
"node_modules/mime-db": {
|
||||||
@@ -5914,6 +5918,18 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/send/node_modules/mime": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"mime": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/send/node_modules/ms": {
|
"node_modules/send/node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"jsonpath": "^1.1.1",
|
"jsonpath": "^1.1.1",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
|
"mime": "^4.0.7",
|
||||||
"node-fetch": "^3.1.0",
|
"node-fetch": "^3.1.0",
|
||||||
"ts-matches": "^6.3.2",
|
"ts-matches": "^6.3.2",
|
||||||
"tslib": "^2.5.3",
|
"tslib": "^2.5.3",
|
||||||
|
|||||||
@@ -400,9 +400,23 @@ export class SystemForEmbassy implements System {
|
|||||||
this.manifest.title.toLowerCase().includes("knots")
|
this.manifest.title.toLowerCase().includes("knots")
|
||||||
)
|
)
|
||||||
version.flavor = "knots"
|
version.flavor = "knots"
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.manifest.id === "lnd" ||
|
||||||
|
this.manifest.id === "ride-the-lightning" ||
|
||||||
|
this.manifest.id === "datum"
|
||||||
|
) {
|
||||||
|
version.upstream.prerelease = ["beta"]
|
||||||
|
} else if (
|
||||||
|
this.manifest.id === "lightning-terminal" ||
|
||||||
|
this.manifest.id === "robosats"
|
||||||
|
) {
|
||||||
|
version.upstream.prerelease = ["alpha"]
|
||||||
|
}
|
||||||
await effects.setDataVersion({
|
await effects.setDataVersion({
|
||||||
version: version.toString(),
|
version: version.toString(),
|
||||||
})
|
})
|
||||||
|
// @FullMetal: package hacks go here
|
||||||
}
|
}
|
||||||
async exportNetwork(effects: Effects) {
|
async exportNetwork(effects: Effects) {
|
||||||
for (const [id, interfaceValue] of Object.entries(
|
for (const [id, interfaceValue] of Object.entries(
|
||||||
|
|||||||
1049
core/Cargo.lock
generated
1049
core/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -296,7 +296,7 @@ pub async fn info(
|
|||||||
}
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref USER_MOUNTS: Mutex<BTreeMap<BackupTargetId, BackupMountGuard<TmpMountGuard>>> =
|
static ref USER_MOUNTS: Mutex<BTreeMap<BackupTargetId, Result<BackupMountGuard<TmpMountGuard>, TmpMountGuard>>> =
|
||||||
Mutex::new(BTreeMap::new());
|
Mutex::new(BTreeMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,8 +305,11 @@ lazy_static::lazy_static! {
|
|||||||
#[command(rename_all = "kebab-case")]
|
#[command(rename_all = "kebab-case")]
|
||||||
pub struct MountParams {
|
pub struct MountParams {
|
||||||
target_id: BackupTargetId,
|
target_id: BackupTargetId,
|
||||||
server_id: String,
|
#[arg(long)]
|
||||||
|
server_id: Option<String>,
|
||||||
password: String,
|
password: String,
|
||||||
|
#[arg(long)]
|
||||||
|
allow_partial: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
@@ -316,24 +319,63 @@ pub async fn mount(
|
|||||||
target_id,
|
target_id,
|
||||||
server_id,
|
server_id,
|
||||||
password,
|
password,
|
||||||
|
allow_partial,
|
||||||
}: MountParams,
|
}: MountParams,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
|
let server_id = if let Some(server_id) = server_id {
|
||||||
|
server_id
|
||||||
|
} else {
|
||||||
|
ctx.db
|
||||||
|
.peek()
|
||||||
|
.await
|
||||||
|
.into_public()
|
||||||
|
.into_server_info()
|
||||||
|
.into_id()
|
||||||
|
.de()?
|
||||||
|
};
|
||||||
|
|
||||||
let mut mounts = USER_MOUNTS.lock().await;
|
let mut mounts = USER_MOUNTS.lock().await;
|
||||||
|
|
||||||
if let Some(existing) = mounts.get(&target_id) {
|
let existing = mounts.get(&target_id);
|
||||||
return Ok(existing.path().display().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let guard = BackupMountGuard::mount(
|
let base = match existing {
|
||||||
TmpMountGuard::mount(&target_id.clone().load(&ctx.db.peek().await)?, ReadWrite).await?,
|
Some(Ok(a)) => return Ok(a.path().display().to_string()),
|
||||||
&server_id,
|
Some(Err(e)) => e.clone(),
|
||||||
&password,
|
None => {
|
||||||
)
|
TmpMountGuard::mount(&target_id.clone().load(&ctx.db.peek().await)?, ReadWrite).await?
|
||||||
.await?;
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let guard = match BackupMountGuard::mount(base.clone(), &server_id, &password).await {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => {
|
||||||
|
if allow_partial {
|
||||||
|
mounts.insert(target_id, Err(base.clone()));
|
||||||
|
let enc_key = BackupMountGuard::<TmpMountGuard>::load_metadata(
|
||||||
|
base.path(),
|
||||||
|
&server_id,
|
||||||
|
&password,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|(_, k)| k);
|
||||||
|
return Err(e)
|
||||||
|
.with_ctx(|e| (
|
||||||
|
e.kind,
|
||||||
|
format!(
|
||||||
|
"\nThe base filesystem did successfully mount at {:?}\nWrapped Key: {:?}",
|
||||||
|
base.path(),
|
||||||
|
enc_key
|
||||||
|
)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let res = guard.path().display().to_string();
|
let res = guard.path().display().to_string();
|
||||||
|
|
||||||
mounts.insert(target_id, guard);
|
mounts.insert(target_id, Ok(guard));
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
@@ -350,11 +392,17 @@ pub async fn umount(_: RpcContext, UmountParams { target_id }: UmountParams) ->
|
|||||||
let mut mounts = USER_MOUNTS.lock().await; // TODO: move to context
|
let mut mounts = USER_MOUNTS.lock().await; // TODO: move to context
|
||||||
if let Some(target_id) = target_id {
|
if let Some(target_id) = target_id {
|
||||||
if let Some(existing) = mounts.remove(&target_id) {
|
if let Some(existing) = mounts.remove(&target_id) {
|
||||||
existing.unmount().await?;
|
match existing {
|
||||||
|
Ok(e) => e.unmount().await?,
|
||||||
|
Err(e) => e.unmount().await?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (_, existing) in std::mem::take(&mut *mounts) {
|
for (_, existing) in std::mem::take(&mut *mounts) {
|
||||||
existing.unmount().await?;
|
match existing {
|
||||||
|
Ok(e) => e.unmount().await?,
|
||||||
|
Err(e) => e.unmount().await?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ pub struct CliContextSeed {
|
|||||||
}
|
}
|
||||||
impl Drop for CliContextSeed {
|
impl Drop for CliContextSeed {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
if let Some(rt) = self.runtime.take() {
|
||||||
|
if let Ok(rt) = Arc::try_unwrap(rt) {
|
||||||
|
rt.shutdown_background();
|
||||||
|
}
|
||||||
|
}
|
||||||
let tmp = format!("{}.tmp", self.cookie_path.display());
|
let tmp = format!("{}.tmp", self.cookie_path.display());
|
||||||
let parent_dir = self.cookie_path.parent().unwrap_or(Path::new("/"));
|
let parent_dir = self.cookie_path.parent().unwrap_or(Path::new("/"));
|
||||||
if !parent_dir.exists() {
|
if !parent_dir.exists() {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub async fn restart(ctx: RpcContext, ControlParams { id }: ControlParams) -> Re
|
|||||||
.await
|
.await
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))?
|
.ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))?
|
||||||
.restart(Guid::new())
|
.restart(Guid::new(), false)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -236,9 +236,10 @@ impl NetworkInterfaceInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize, TS, HasModel)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[model = "Model<Self>"]
|
||||||
pub struct IpInfo {
|
pub struct IpInfo {
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub name: InternedString,
|
pub name: InternedString,
|
||||||
|
|||||||
@@ -29,12 +29,11 @@ pub struct BackupMountGuard<G: GenericMountGuard> {
|
|||||||
}
|
}
|
||||||
impl<G: GenericMountGuard> BackupMountGuard<G> {
|
impl<G: GenericMountGuard> BackupMountGuard<G> {
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn mount(
|
pub async fn load_metadata(
|
||||||
backup_disk_mount_guard: G,
|
backup_disk_path: &Path,
|
||||||
server_id: &str,
|
server_id: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<(StartOsRecoveryInfo, String), Error> {
|
||||||
let backup_disk_path = backup_disk_mount_guard.path();
|
|
||||||
let backup_dir = backup_disk_path.join("StartOSBackups").join(server_id);
|
let backup_dir = backup_disk_path.join("StartOSBackups").join(server_id);
|
||||||
let unencrypted_metadata_path = backup_dir.join("unencrypted-metadata.json");
|
let unencrypted_metadata_path = backup_dir.join("unencrypted-metadata.json");
|
||||||
let crypt_path = backup_dir.join("crypt");
|
let crypt_path = backup_dir.join("crypt");
|
||||||
@@ -79,7 +78,6 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
|
|||||||
&rand::random::<[u8; 32]>()[..],
|
&rand::random::<[u8; 32]>()[..],
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
if unencrypted_metadata.password_hash.is_none() {
|
if unencrypted_metadata.password_hash.is_none() {
|
||||||
unencrypted_metadata.password_hash = Some(
|
unencrypted_metadata.password_hash = Some(
|
||||||
argon2::hash_encoded(
|
argon2::hash_encoded(
|
||||||
@@ -96,6 +94,20 @@ impl<G: GenericMountGuard> BackupMountGuard<G> {
|
|||||||
&encrypt_slice(&enc_key, password),
|
&encrypt_slice(&enc_key, password),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
Ok((unencrypted_metadata, enc_key))
|
||||||
|
}
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn mount(
|
||||||
|
backup_disk_mount_guard: G,
|
||||||
|
server_id: &str,
|
||||||
|
password: &str,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let backup_disk_path = backup_disk_mount_guard.path();
|
||||||
|
let (unencrypted_metadata, enc_key) =
|
||||||
|
Self::load_metadata(backup_disk_path, server_id, password).await?;
|
||||||
|
let backup_dir = backup_disk_path.join("StartOSBackups").join(server_id);
|
||||||
|
let unencrypted_metadata_path = backup_dir.join("unencrypted-metadata.json");
|
||||||
|
let crypt_path = backup_dir.join("crypt");
|
||||||
|
|
||||||
if tokio::fs::metadata(&crypt_path).await.is_err() {
|
if tokio::fs::metadata(&crypt_path).await.is_err() {
|
||||||
tokio::fs::create_dir_all(&crypt_path).await.with_ctx(|_| {
|
tokio::fs::create_dir_all(&crypt_path).await.with_ctx(|_| {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use digest::generic_array::GenericArray;
|
use digest::generic_array::GenericArray;
|
||||||
@@ -54,7 +55,30 @@ impl<Fs: FileSystem> FileSystem for IdMapped<Fs> {
|
|||||||
self.filesystem.source().await
|
self.filesystem.source().await
|
||||||
}
|
}
|
||||||
async fn pre_mount(&self, mountpoint: &Path) -> Result<(), Error> {
|
async fn pre_mount(&self, mountpoint: &Path) -> Result<(), Error> {
|
||||||
self.filesystem.pre_mount(mountpoint).await
|
self.filesystem.pre_mount(mountpoint).await?;
|
||||||
|
let info = tokio::fs::metadata(mountpoint).await?;
|
||||||
|
let uid_in_range = self.from_id <= info.uid() && self.from_id + self.range > info.uid();
|
||||||
|
let gid_in_range = self.from_id <= info.gid() && self.from_id + self.range > info.gid();
|
||||||
|
if uid_in_range || gid_in_range {
|
||||||
|
Command::new("chown")
|
||||||
|
.arg(format!(
|
||||||
|
"{uid}:{gid}",
|
||||||
|
uid = if uid_in_range {
|
||||||
|
self.to_id + info.uid() - self.from_id
|
||||||
|
} else {
|
||||||
|
info.uid()
|
||||||
|
},
|
||||||
|
gid = if gid_in_range {
|
||||||
|
self.to_id + info.gid() - self.from_id
|
||||||
|
} else {
|
||||||
|
info.gid()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.arg(&mountpoint)
|
||||||
|
.invoke(crate::ErrorKind::Filesystem)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn mount<P: AsRef<Path> + Send>(
|
async fn mount<P: AsRef<Path> + Send>(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -179,12 +179,6 @@ impl LxcContainer {
|
|||||||
.await?;
|
.await?;
|
||||||
// TODO: append config
|
// TODO: append config
|
||||||
let rootfs_dir = container_dir.join("rootfs");
|
let rootfs_dir = container_dir.join("rootfs");
|
||||||
tokio::fs::create_dir_all(&rootfs_dir).await?;
|
|
||||||
Command::new("chown")
|
|
||||||
.arg("100000:100000")
|
|
||||||
.arg(&rootfs_dir)
|
|
||||||
.invoke(ErrorKind::Filesystem)
|
|
||||||
.await?;
|
|
||||||
let rootfs = OverlayGuard::mount(
|
let rootfs = OverlayGuard::mount(
|
||||||
TmpMountGuard::mount(
|
TmpMountGuard::mount(
|
||||||
&IdMapped::new(
|
&IdMapped::new(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::net::SocketAddr;
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
@@ -52,10 +52,13 @@ struct ForwardState {
|
|||||||
current: BTreeMap<u16, BTreeMap<InternedString, SocketAddr>>,
|
current: BTreeMap<u16, BTreeMap<InternedString, SocketAddr>>,
|
||||||
}
|
}
|
||||||
impl ForwardState {
|
impl ForwardState {
|
||||||
async fn sync(&mut self, interfaces: &BTreeMap<InternedString, bool>) -> Result<(), Error> {
|
async fn sync(
|
||||||
|
&mut self,
|
||||||
|
interfaces: &BTreeMap<InternedString, (bool, Vec<Ipv4Addr>)>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let private_interfaces = interfaces
|
let private_interfaces = interfaces
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, public)| !*public)
|
.filter(|(_, (public, _))| !*public)
|
||||||
.map(|(i, _)| i)
|
.map(|(i, _)| i)
|
||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeSet<_>>();
|
||||||
let all_interfaces = interfaces.keys().collect::<BTreeSet<_>>();
|
let all_interfaces = interfaces.keys().collect::<BTreeSet<_>>();
|
||||||
@@ -81,26 +84,30 @@ impl ForwardState {
|
|||||||
let mut to_rm = actual
|
let mut to_rm = actual
|
||||||
.difference(expected)
|
.difference(expected)
|
||||||
.copied()
|
.copied()
|
||||||
.cloned()
|
.map(|i| (i.clone(), &interfaces[i].1))
|
||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeMap<_, _>>();
|
||||||
let mut to_add = expected
|
let mut to_add = expected
|
||||||
.difference(&actual)
|
.difference(&actual)
|
||||||
.copied()
|
.copied()
|
||||||
.cloned()
|
.map(|i| (i.clone(), &interfaces[i].1))
|
||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeMap<_, _>>();
|
||||||
for interface in actual.intersection(expected).copied() {
|
for interface in actual.intersection(expected).copied() {
|
||||||
if cur[interface] != req.target {
|
if cur[interface] != req.target {
|
||||||
to_rm.insert(interface.clone());
|
to_rm.insert(interface.clone(), &interfaces[interface].1);
|
||||||
to_add.insert(interface.clone());
|
to_add.insert(interface.clone(), &interfaces[interface].1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for interface in to_rm {
|
for (interface, ips) in to_rm {
|
||||||
unforward(external, &*interface, cur[&interface]).await?;
|
for ip in ips {
|
||||||
|
unforward(&*interface, (*ip, external).into(), cur[&interface]).await?;
|
||||||
|
}
|
||||||
cur.remove(&interface);
|
cur.remove(&interface);
|
||||||
}
|
}
|
||||||
for interface in to_add {
|
for (interface, ips) in to_add {
|
||||||
forward(external, &*interface, req.target).await?;
|
cur.insert(interface.clone(), req.target);
|
||||||
cur.insert(interface, req.target);
|
for ip in ips {
|
||||||
|
forward(&*interface, (*ip, external).into(), cur[&interface]).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Some(req), None) => {
|
(Some(req), None) => {
|
||||||
@@ -112,16 +119,19 @@ impl ForwardState {
|
|||||||
}
|
}
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.copied()
|
.copied()
|
||||||
.cloned()
|
|
||||||
{
|
{
|
||||||
forward(external, &*interface, req.target).await?;
|
cur.insert(interface.clone(), req.target);
|
||||||
cur.insert(interface, req.target);
|
for ip in &interfaces[interface].1 {
|
||||||
|
forward(&**interface, (*ip, external).into(), req.target).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(None, Some(cur)) => {
|
(None, Some(cur)) => {
|
||||||
let to_rm = cur.keys().cloned().collect::<BTreeSet<_>>();
|
let to_rm = cur.keys().cloned().collect::<BTreeSet<_>>();
|
||||||
for interface in to_rm {
|
for interface in to_rm {
|
||||||
unforward(external, &*interface, cur[&interface]).await?;
|
for ip in &interfaces[&interface].1 {
|
||||||
|
unforward(&*interface, (*ip, external).into(), cur[&interface]).await?;
|
||||||
|
}
|
||||||
cur.remove(&interface);
|
cur.remove(&interface);
|
||||||
}
|
}
|
||||||
self.current.remove(&external);
|
self.current.remove(&external);
|
||||||
@@ -155,7 +165,26 @@ impl LanPortForwardController {
|
|||||||
let mut interfaces = ip_info.peek_and_mark_seen(|ip_info| {
|
let mut interfaces = ip_info.peek_and_mark_seen(|ip_info| {
|
||||||
ip_info
|
ip_info
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(iface, info)| (iface.clone(), info.inbound()))
|
.map(|(iface, info)| {
|
||||||
|
(
|
||||||
|
iface.clone(),
|
||||||
|
(
|
||||||
|
info.inbound(),
|
||||||
|
info.ip_info.as_ref().map_or(Vec::new(), |i| {
|
||||||
|
i.subnets
|
||||||
|
.iter()
|
||||||
|
.filter_map(|s| {
|
||||||
|
if let IpAddr::V4(ip) = s.addr() {
|
||||||
|
Some(ip)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
});
|
});
|
||||||
let mut reply: Option<oneshot::Sender<Result<(), Error>>> = None;
|
let mut reply: Option<oneshot::Sender<Result<(), Error>>> = None;
|
||||||
@@ -175,7 +204,21 @@ impl LanPortForwardController {
|
|||||||
interfaces = ip_info.peek(|ip_info| {
|
interfaces = ip_info.peek(|ip_info| {
|
||||||
ip_info
|
ip_info
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(iface, info)| (iface.clone(), info.inbound()))
|
.map(|(iface, info)| (iface.clone(), (
|
||||||
|
info.inbound(),
|
||||||
|
info.ip_info.as_ref().map_or(Vec::new(), |i| {
|
||||||
|
i.subnets
|
||||||
|
.iter()
|
||||||
|
.filter_map(|s| {
|
||||||
|
if let IpAddr::V4(ip) = s.addr() {
|
||||||
|
Some(ip)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}),
|
||||||
|
)))
|
||||||
.collect()
|
.collect()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -222,87 +265,29 @@ impl LanPortForwardController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// iptables -t nat -A POSTROUTING -s 10.59.0.0/24 ! -d 10.59.0.0/24 -j SNAT --to $ip
|
async fn forward(interface: &str, source: SocketAddr, target: SocketAddr) -> Result<(), Error> {
|
||||||
// iptables -I INPUT -p udp --dport $port -j ACCEPT
|
Command::new("/usr/lib/startos/scripts/forward-port")
|
||||||
// iptables -I FORWARD -s 10.59.0.0/24 -j ACCEPT
|
.env("iiface", interface)
|
||||||
async fn forward(external: u16, interface: &str, target: SocketAddr) -> Result<(), Error> {
|
.env("oiface", START9_BRIDGE_IFACE)
|
||||||
for proto in ["tcp", "udp"] {
|
.env("sip", source.ip().to_string())
|
||||||
Command::new("iptables")
|
.env("dip", target.ip().to_string())
|
||||||
.arg("-I")
|
.env("sport", source.port().to_string())
|
||||||
.arg("FORWARD")
|
.env("dport", target.port().to_string())
|
||||||
.arg("-i")
|
.invoke(ErrorKind::Network)
|
||||||
.arg(interface)
|
.await?;
|
||||||
.arg("-o")
|
|
||||||
.arg(START9_BRIDGE_IFACE)
|
|
||||||
.arg("-p")
|
|
||||||
.arg(proto)
|
|
||||||
.arg("-d")
|
|
||||||
.arg(target.ip().to_string())
|
|
||||||
.arg("--dport")
|
|
||||||
.arg(target.port().to_string())
|
|
||||||
.arg("-j")
|
|
||||||
.arg("ACCEPT")
|
|
||||||
.invoke(crate::ErrorKind::Network)
|
|
||||||
.await?;
|
|
||||||
Command::new("iptables")
|
|
||||||
.arg("-t")
|
|
||||||
.arg("nat")
|
|
||||||
.arg("-I")
|
|
||||||
.arg("PREROUTING")
|
|
||||||
.arg("-i")
|
|
||||||
.arg(interface)
|
|
||||||
.arg("-p")
|
|
||||||
.arg(proto)
|
|
||||||
.arg("--dport")
|
|
||||||
.arg(external.to_string())
|
|
||||||
.arg("-j")
|
|
||||||
.arg("DNAT")
|
|
||||||
.arg("--to")
|
|
||||||
.arg(target.to_string())
|
|
||||||
.invoke(crate::ErrorKind::Network)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// iptables -D FORWARD -o br-start9 -p tcp -d 172.18.0.2 --dport 8333 -j ACCEPT
|
async fn unforward(interface: &str, source: SocketAddr, target: SocketAddr) -> Result<(), Error> {
|
||||||
// iptables -t nat -D PREROUTING -p tcp --dport 32768 -j DNAT --to 172.18.0.2:8333
|
Command::new("/usr/lib/startos/scripts/forward-port")
|
||||||
async fn unforward(external: u16, interface: &str, target: SocketAddr) -> Result<(), Error> {
|
.env("UNDO", "1")
|
||||||
for proto in ["tcp", "udp"] {
|
.env("iiface", interface)
|
||||||
Command::new("iptables")
|
.env("oiface", START9_BRIDGE_IFACE)
|
||||||
.arg("-D")
|
.env("sip", source.ip().to_string())
|
||||||
.arg("FORWARD")
|
.env("dip", target.ip().to_string())
|
||||||
.arg("-i")
|
.env("sport", source.port().to_string())
|
||||||
.arg(interface)
|
.env("dport", target.port().to_string())
|
||||||
.arg("-o")
|
.invoke(ErrorKind::Network)
|
||||||
.arg(START9_BRIDGE_IFACE)
|
.await?;
|
||||||
.arg("-p")
|
|
||||||
.arg(proto)
|
|
||||||
.arg("-d")
|
|
||||||
.arg(target.ip().to_string())
|
|
||||||
.arg("--dport")
|
|
||||||
.arg(target.port().to_string())
|
|
||||||
.arg("-j")
|
|
||||||
.arg("ACCEPT")
|
|
||||||
.invoke(crate::ErrorKind::Network)
|
|
||||||
.await?;
|
|
||||||
Command::new("iptables")
|
|
||||||
.arg("-t")
|
|
||||||
.arg("nat")
|
|
||||||
.arg("-D")
|
|
||||||
.arg("PREROUTING")
|
|
||||||
.arg("-i")
|
|
||||||
.arg(interface)
|
|
||||||
.arg("-p")
|
|
||||||
.arg(proto)
|
|
||||||
.arg("--dport")
|
|
||||||
.arg(external.to_string())
|
|
||||||
.arg("-j")
|
|
||||||
.arg("DNAT")
|
|
||||||
.arg("--to")
|
|
||||||
.arg(target.to_string())
|
|
||||||
.invoke(crate::ErrorKind::Network)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ impl NetServiceData {
|
|||||||
let mut bind_hostname_info = hostname_info.remove(internal).unwrap_or_default();
|
let mut bind_hostname_info = hostname_info.remove(internal).unwrap_or_default();
|
||||||
bind_hostname_info.push(HostnameInfo::Onion {
|
bind_hostname_info.push(HostnameInfo::Onion {
|
||||||
hostname: OnionHostname {
|
hostname: OnionHostname {
|
||||||
value: tor_addr.to_string(),
|
value: InternedString::from_display(tor_addr),
|
||||||
port: ports.non_ssl,
|
port: ports.non_ssl,
|
||||||
ssl_port: ports.ssl,
|
ssl_port: ports.ssl,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
|
use lazy_format::lazy_format;
|
||||||
use models::{HostId, ServiceInterfaceId};
|
use models::{HostId, ServiceInterfaceId};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
@@ -21,15 +22,29 @@ pub enum HostnameInfo {
|
|||||||
hostname: OnionHostname,
|
hostname: OnionHostname,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
impl HostnameInfo {
|
||||||
|
pub fn to_san_hostname(&self) -> InternedString {
|
||||||
|
match self {
|
||||||
|
Self::Ip { hostname, .. } => hostname.to_san_hostname(),
|
||||||
|
Self::Onion { hostname } => hostname.to_san_hostname(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct OnionHostname {
|
pub struct OnionHostname {
|
||||||
pub value: String,
|
#[ts(type = "string")]
|
||||||
|
pub value: InternedString,
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
pub ssl_port: Option<u16>,
|
pub ssl_port: Option<u16>,
|
||||||
}
|
}
|
||||||
|
impl OnionHostname {
|
||||||
|
pub fn to_san_hostname(&self) -> InternedString {
|
||||||
|
self.value.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@@ -64,6 +79,24 @@ pub enum IpHostname {
|
|||||||
ssl_port: Option<u16>,
|
ssl_port: Option<u16>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
impl IpHostname {
|
||||||
|
pub fn to_san_hostname(&self) -> InternedString {
|
||||||
|
match self {
|
||||||
|
Self::Ipv4 { value, .. } => InternedString::from_display(value),
|
||||||
|
Self::Ipv6 { value, .. } => InternedString::from_display(value),
|
||||||
|
Self::Local { value, .. } => value.clone(),
|
||||||
|
Self::Domain {
|
||||||
|
domain, subdomain, ..
|
||||||
|
} => {
|
||||||
|
if let Some(subdomain) = subdomain {
|
||||||
|
InternedString::from_display(&lazy_format!("{subdomain}.{domain}"))
|
||||||
|
} else {
|
||||||
|
domain.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
|
|||||||
@@ -188,14 +188,22 @@ impl TryFrom<ManifestV1> for Manifest {
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
fn try_from(value: ManifestV1) -> Result<Self, Self::Error> {
|
fn try_from(value: ManifestV1) -> Result<Self, Self::Error> {
|
||||||
let default_url = value.upstream_repo.clone();
|
let default_url = value.upstream_repo.clone();
|
||||||
|
let mut version = ExtendedVersion::from(
|
||||||
|
exver::emver::Version::from_str(&value.version)
|
||||||
|
.with_kind(ErrorKind::Deserialization)?,
|
||||||
|
);
|
||||||
|
if &*value.id == "bitcoind" && value.title.to_ascii_lowercase().contains("knots") {
|
||||||
|
version = version.with_flavor("knots");
|
||||||
|
} else if &*value.id == "lnd" || &*value.id == "ride-the-lightning" || &*value.id == "datum"
|
||||||
|
{
|
||||||
|
version = version.map_upstream(|mut v| v.with_prerelease(["beta".into()]));
|
||||||
|
} else if &*value.id == "lightning-terminal" || &*value.id == "robosats" {
|
||||||
|
version = version.map_upstream(|mut v| v.with_prerelease(["alpha".into()]));
|
||||||
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id: value.id,
|
id: value.id,
|
||||||
title: format!("{} (Legacy)", value.title).into(),
|
title: format!("{} (Legacy)", value.title).into(),
|
||||||
version: ExtendedVersion::from(
|
version: version.into(),
|
||||||
exver::emver::Version::from_str(&value.version)
|
|
||||||
.with_kind(ErrorKind::Deserialization)?,
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
satisfies: BTreeSet::new(),
|
satisfies: BTreeSet::new(),
|
||||||
release_notes: value.release_notes,
|
release_notes: value.release_notes,
|
||||||
can_migrate_from: VersionRange::any(),
|
can_migrate_from: VersionRange::any(),
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub async fn restart(
|
|||||||
ProcedureId { procedure_id }: ProcedureId,
|
ProcedureId { procedure_id }: ProcedureId,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let context = context.deref()?;
|
let context = context.deref()?;
|
||||||
context.restart(procedure_id).await?;
|
context.restart(procedure_id, false).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,11 +71,6 @@ pub async fn mount(
|
|||||||
if is_mountpoint(&mountpoint).await? {
|
if is_mountpoint(&mountpoint).await? {
|
||||||
unmount(&mountpoint, true).await?;
|
unmount(&mountpoint, true).await?;
|
||||||
}
|
}
|
||||||
Command::new("chown")
|
|
||||||
.arg("100000:100000")
|
|
||||||
.arg(&mountpoint)
|
|
||||||
.invoke(crate::ErrorKind::Filesystem)
|
|
||||||
.await?;
|
|
||||||
IdMapped::new(Bind::new(source).with_type(filetype), 0, 100000, 65536)
|
IdMapped::new(Bind::new(source).with_type(filetype), 0, 100000, 65536)
|
||||||
.mount(
|
.mount(
|
||||||
mountpoint,
|
mountpoint,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use imbl_value::InternedString;
|
use imbl_value::InternedString;
|
||||||
|
use ipnet::IpNet;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
|
|
||||||
@@ -8,6 +10,7 @@ use crate::service::effects::callbacks::CallbackHandler;
|
|||||||
use crate::service::effects::prelude::*;
|
use crate::service::effects::prelude::*;
|
||||||
use crate::service::rpc::CallbackId;
|
use crate::service::rpc::CallbackId;
|
||||||
use crate::util::serde::Pem;
|
use crate::util::serde::Pem;
|
||||||
|
use crate::HOST_IP;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, TS, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, TS, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -57,6 +60,13 @@ pub async fn get_ssl_certificate(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(InternedString::from_display)
|
.map(InternedString::from_display)
|
||||||
.chain(m.as_domains().keys()?)
|
.chain(m.as_domains().keys()?)
|
||||||
|
.chain(
|
||||||
|
m.as_hostname_info()
|
||||||
|
.de()?
|
||||||
|
.values()
|
||||||
|
.flatten()
|
||||||
|
.map(|h| h.to_san_hostname()),
|
||||||
|
)
|
||||||
.collect::<Vec<_>>())
|
.collect::<Vec<_>>())
|
||||||
})
|
})
|
||||||
.map(|a| a.and_then(|a| a))
|
.map(|a| a.and_then(|a| a))
|
||||||
@@ -70,6 +80,28 @@ pub async fn get_ssl_certificate(
|
|||||||
if !packages.contains(internal) {
|
if !packages.contains(internal) {
|
||||||
return Err(errfn(&*hostname));
|
return Err(errfn(&*hostname));
|
||||||
}
|
}
|
||||||
|
} else if let Ok(ip) = hostname.parse::<IpAddr>() {
|
||||||
|
if IpNet::new(HOST_IP.into(), 24)
|
||||||
|
.with_kind(ErrorKind::ParseNetAddress)?
|
||||||
|
.contains(&ip)
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
} else if db
|
||||||
|
.as_public()
|
||||||
|
.as_server_info()
|
||||||
|
.as_network()
|
||||||
|
.as_network_interfaces()
|
||||||
|
.as_entries()?
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|(_, net)| net.as_ip_info().transpose_ref())
|
||||||
|
.flat_map(|net| net.as_subnets().de().log_err())
|
||||||
|
.flatten()
|
||||||
|
.any(|s| s.addr() == ip)
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(errfn(&*hostname))
|
||||||
|
}?;
|
||||||
} else {
|
} else {
|
||||||
if !allowed_hostnames.contains(hostname) {
|
if !allowed_hostnames.contains(hostname) {
|
||||||
return Err(errfn(&*hostname));
|
return Err(errfn(&*hostname));
|
||||||
@@ -128,6 +160,11 @@ pub async fn get_ssl_key(
|
|||||||
let context = context.deref()?;
|
let context = context.deref()?;
|
||||||
let package_id = &context.seed.id;
|
let package_id = &context.seed.id;
|
||||||
let algorithm = algorithm.unwrap_or(Algorithm::Ecdsa);
|
let algorithm = algorithm.unwrap_or(Algorithm::Ecdsa);
|
||||||
|
let container_ip = if let Some(lxc) = context.seed.persistent_container.lxc_container.get() {
|
||||||
|
Some(lxc.ip().await?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let cert = context
|
let cert = context
|
||||||
.seed
|
.seed
|
||||||
@@ -135,7 +172,7 @@ pub async fn get_ssl_key(
|
|||||||
.db
|
.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
let errfn = |h: &str| Error::new(eyre!("unknown hostname: {h}"), ErrorKind::NotFound);
|
let errfn = |h: &str| Error::new(eyre!("unknown hostname: {h}"), ErrorKind::NotFound);
|
||||||
let allowed_hostnames = db
|
let mut allowed_hostnames = db
|
||||||
.as_public()
|
.as_public()
|
||||||
.as_package_data()
|
.as_package_data()
|
||||||
.as_idx(package_id)
|
.as_idx(package_id)
|
||||||
@@ -148,11 +185,19 @@ pub async fn get_ssl_key(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(InternedString::from_display)
|
.map(InternedString::from_display)
|
||||||
.chain(m.as_domains().keys()?)
|
.chain(m.as_domains().keys()?)
|
||||||
|
.chain(
|
||||||
|
m.as_hostname_info()
|
||||||
|
.de()?
|
||||||
|
.values()
|
||||||
|
.flatten()
|
||||||
|
.map(|h| h.to_san_hostname()),
|
||||||
|
)
|
||||||
.collect::<Vec<_>>())
|
.collect::<Vec<_>>())
|
||||||
})
|
})
|
||||||
.map(|a| a.and_then(|a| a))
|
.map(|a| a.and_then(|a| a))
|
||||||
.flatten_ok()
|
.flatten_ok()
|
||||||
.try_collect::<_, BTreeSet<_>, _>()?;
|
.try_collect::<_, BTreeSet<_>, _>()?;
|
||||||
|
allowed_hostnames.extend(container_ip.as_ref().map(InternedString::from_display));
|
||||||
for hostname in &hostnames {
|
for hostname in &hostnames {
|
||||||
if let Some(internal) = hostname
|
if let Some(internal) = hostname
|
||||||
.strip_suffix(".embassy")
|
.strip_suffix(".embassy")
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
-----BEGIN EC PRIVATE KEY-----
|
|
||||||
MHcCAQEEINn5jiv9VFgEwdUJsDksSTAjPKwkl2DCmCmumu4D1GnNoAoGCCqGSM49
|
|
||||||
AwEHoUQDQgAE5KuqP+Wdn8pzmNMxK2hya6mKj1H0j5b47y97tIXqf5ajTi8koRPl
|
|
||||||
yao3YcqdtBtN37aw4rVlXVwEJIozZgyiyA==
|
|
||||||
-----END EC PRIVATE KEY-----
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIB9DCCAZmgAwIBAgIUIWsFiA8JqIqeUo+Psn91oCQIcdwwCgYIKoZIzj0EAwIw
|
|
||||||
TzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRowGAYDVQQKDBFTdGFydDkgTGFi
|
|
||||||
cywgSW5jLjEXMBUGA1UEAwwOZmFrZW5hbWUubG9jYWwwHhcNMjQwMjE0MTk1MTUz
|
|
||||||
WhcNMjUwMjEzMTk1MTUzWjBPMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xGjAY
|
|
||||||
BgNVBAoMEVN0YXJ0OSBMYWJzLCBJbmMuMRcwFQYDVQQDDA5mYWtlbmFtZS5sb2Nh
|
|
||||||
bDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOSrqj/lnZ/Kc5jTMStocmupio9R
|
|
||||||
9I+W+O8ve7SF6n+Wo04vJKET5cmqN2HKnbQbTd+2sOK1ZV1cBCSKM2YMosijUzBR
|
|
||||||
MB0GA1UdDgQWBBR+qd4W//H34Eg90yAPjYz3nZK79DAfBgNVHSMEGDAWgBR+qd4W
|
|
||||||
//H34Eg90yAPjYz3nZK79DAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kA
|
|
||||||
MEYCIQDNSN9YWkGbntG+nC+NzEyqE9FcvYZ8TaF3sOnthqSVKwIhAM2N+WJG/p4C
|
|
||||||
cPl4HSPPgDaOIhVZzxSje2ycb7wvFtpH
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1142,23 +1142,6 @@ pub async fn cli_attach(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let (kill, thread_kill) = tokio::sync::oneshot::channel();
|
|
||||||
let (thread_send, recv) = tokio::sync::mpsc::channel(4 * CAP_1_KiB);
|
|
||||||
let stdin_thread: NonDetachingJoinHandle<()> = tokio::task::spawn_blocking(move || {
|
|
||||||
use std::io::Read;
|
|
||||||
let mut stdin = stdin.lock().bytes();
|
|
||||||
|
|
||||||
while thread_kill.is_empty() {
|
|
||||||
if let Some(b) = stdin.next() {
|
|
||||||
thread_send.blocking_send(b).unwrap();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into();
|
|
||||||
let mut stdin = Some(recv);
|
|
||||||
|
|
||||||
let guid: Guid = from_value(
|
let guid: Guid = from_value(
|
||||||
context
|
context
|
||||||
.call_remote::<RpcContext>(
|
.call_remote::<RpcContext>(
|
||||||
@@ -1178,6 +1161,25 @@ pub async fn cli_attach(
|
|||||||
)?;
|
)?;
|
||||||
let mut ws = context.ws_continuation(guid).await?;
|
let mut ws = context.ws_continuation(guid).await?;
|
||||||
|
|
||||||
|
let (kill, thread_kill) = tokio::sync::oneshot::channel();
|
||||||
|
let (thread_send, recv) = tokio::sync::mpsc::channel(4 * CAP_1_KiB);
|
||||||
|
let stdin_thread: NonDetachingJoinHandle<()> = tokio::task::spawn_blocking(move || {
|
||||||
|
use std::io::Read;
|
||||||
|
let mut stdin = stdin.lock().bytes();
|
||||||
|
|
||||||
|
while thread_kill.is_empty() {
|
||||||
|
if let Some(b) = stdin.next() {
|
||||||
|
if let Err(_) = thread_send.blocking_send(b) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
let mut stdin = Some(recv);
|
||||||
|
|
||||||
let mut current_in = "stdin";
|
let mut current_in = "stdin";
|
||||||
let mut current_out = "stdout".to_owned();
|
let mut current_out = "stdout".to_owned();
|
||||||
ws.send(Message::Text(current_in.into()))
|
ws.send(Message::Text(current_in.into()))
|
||||||
|
|||||||
@@ -161,12 +161,6 @@ impl PersistentContainer {
|
|||||||
.rootfs_dir()
|
.rootfs_dir()
|
||||||
.join("media/startos/volumes")
|
.join("media/startos/volumes")
|
||||||
.join(volume);
|
.join(volume);
|
||||||
tokio::fs::create_dir_all(&mountpoint).await?;
|
|
||||||
Command::new("chown")
|
|
||||||
.arg("100000:100000")
|
|
||||||
.arg(&mountpoint)
|
|
||||||
.invoke(crate::ErrorKind::Filesystem)
|
|
||||||
.await?;
|
|
||||||
let mount = MountGuard::mount(
|
let mount = MountGuard::mount(
|
||||||
&IdMapped::new(
|
&IdMapped::new(
|
||||||
Bind::new(data_dir(DATA_DIR, &s9pk.as_manifest().id, volume)),
|
Bind::new(data_dir(DATA_DIR, &s9pk.as_manifest().id, volume)),
|
||||||
@@ -182,12 +176,6 @@ impl PersistentContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mountpoint = lxc_container.rootfs_dir().join("media/startos/assets");
|
let mountpoint = lxc_container.rootfs_dir().join("media/startos/assets");
|
||||||
tokio::fs::create_dir_all(&mountpoint).await?;
|
|
||||||
Command::new("chown")
|
|
||||||
.arg("100000:100000")
|
|
||||||
.arg(&mountpoint)
|
|
||||||
.invoke(crate::ErrorKind::Filesystem)
|
|
||||||
.await?;
|
|
||||||
let assets = if let Some(sqfs) = s9pk
|
let assets = if let Some(sqfs) = s9pk
|
||||||
.as_archive()
|
.as_archive()
|
||||||
.contents()
|
.contents()
|
||||||
@@ -215,12 +203,6 @@ impl PersistentContainer {
|
|||||||
.rootfs_dir()
|
.rootfs_dir()
|
||||||
.join("media/startos/assets")
|
.join("media/startos/assets")
|
||||||
.join(Path::new(asset).with_extension(""));
|
.join(Path::new(asset).with_extension(""));
|
||||||
tokio::fs::create_dir_all(&mountpoint).await?;
|
|
||||||
Command::new("chown")
|
|
||||||
.arg("100000:100000")
|
|
||||||
.arg(&mountpoint)
|
|
||||||
.invoke(crate::ErrorKind::Filesystem)
|
|
||||||
.await?;
|
|
||||||
let Some(sqfs) = entry.as_file() else {
|
let Some(sqfs) = entry.as_file() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@@ -271,12 +253,6 @@ impl PersistentContainer {
|
|||||||
.and_then(|e| e.as_file())
|
.and_then(|e| e.as_file())
|
||||||
.or_not_found(sqfs_path.display())?;
|
.or_not_found(sqfs_path.display())?;
|
||||||
let mountpoint = image_path.join(image);
|
let mountpoint = image_path.join(image);
|
||||||
tokio::fs::create_dir_all(&mountpoint).await?;
|
|
||||||
Command::new("chown")
|
|
||||||
.arg("100000:100000")
|
|
||||||
.arg(&mountpoint)
|
|
||||||
.invoke(ErrorKind::Filesystem)
|
|
||||||
.await?;
|
|
||||||
images.insert(
|
images.insert(
|
||||||
image.clone(),
|
image.clone(),
|
||||||
Arc::new(
|
Arc::new(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use futures::future::BoxFuture;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
use super::TempDesiredRestore;
|
use super::TempDesiredRestore;
|
||||||
@@ -12,7 +13,7 @@ use crate::util::future::RemoteCancellable;
|
|||||||
|
|
||||||
pub(super) struct Restart;
|
pub(super) struct Restart;
|
||||||
impl Handler<Restart> for ServiceActor {
|
impl Handler<Restart> for ServiceActor {
|
||||||
type Response = ();
|
type Response = BoxFuture<'static, Option<()>>;
|
||||||
fn conflicts_with(_: &Restart) -> ConflictBuilder<Self> {
|
fn conflicts_with(_: &Restart) -> ConflictBuilder<Self> {
|
||||||
ConflictBuilder::everything().except::<GetActionInput>()
|
ConflictBuilder::everything().except::<GetActionInput>()
|
||||||
}
|
}
|
||||||
@@ -65,14 +66,18 @@ impl Handler<Restart> for ServiceActor {
|
|||||||
if let Some(t) = old {
|
if let Some(t) = old {
|
||||||
t.abort().await;
|
t.abort().await;
|
||||||
}
|
}
|
||||||
if transition.await.is_none() {
|
transition.boxed()
|
||||||
tracing::warn!("Service {} has been cancelled", &self.0.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Service {
|
impl Service {
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn restart(&self, id: Guid) -> Result<(), Error> {
|
pub async fn restart(&self, id: Guid, wait: bool) -> Result<(), Error> {
|
||||||
self.actor.send(id, Restart).await
|
let fut = self.actor.send(id, Restart).await?;
|
||||||
|
if wait {
|
||||||
|
if fut.await.is_none() {
|
||||||
|
tracing::warn!("Restart has been cancelled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ struct ConcurrentRunner<A> {
|
|||||||
waiting: Vec<Request<A>>,
|
waiting: Vec<Request<A>>,
|
||||||
recv: mpsc::UnboundedReceiver<Request<A>>,
|
recv: mpsc::UnboundedReceiver<Request<A>>,
|
||||||
handlers: Vec<(
|
handlers: Vec<(
|
||||||
|
&'static str,
|
||||||
Guid,
|
Guid,
|
||||||
Arc<ConflictFn<A>>,
|
Arc<ConflictFn<A>>,
|
||||||
oneshot::Sender<Box<dyn Any + Send>>,
|
oneshot::Sender<Box<dyn Any + Send>>,
|
||||||
@@ -47,13 +48,26 @@ impl<A: Actor + Clone> Future for ConcurrentRunner<A> {
|
|||||||
if this
|
if this
|
||||||
.handlers
|
.handlers
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(hid, f, _, _)| &id != hid && f(&*msg))
|
.any(|(_, hid, f, _, _)| &id != hid && f(&*msg))
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "unstable")]
|
||||||
|
{
|
||||||
|
tracing::debug!("{} must wait...", msg.type_name());
|
||||||
|
tracing::debug!(
|
||||||
|
"waiting on {:?}",
|
||||||
|
this.handlers
|
||||||
|
.iter()
|
||||||
|
.filter(|h| h.2(&*msg))
|
||||||
|
.map(|h| (h.0, &h.1))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
this.waiting.push((id, msg, reply));
|
this.waiting.push((id, msg, reply));
|
||||||
} else {
|
} else {
|
||||||
let mut actor = this.actor.clone();
|
let mut actor = this.actor.clone();
|
||||||
let queue = this.queue.clone();
|
let queue = this.queue.clone();
|
||||||
this.handlers.push((
|
this.handlers.push((
|
||||||
|
msg.type_name(),
|
||||||
id.clone(),
|
id.clone(),
|
||||||
msg.conflicts_with(),
|
msg.conflicts_with(),
|
||||||
reply,
|
reply,
|
||||||
@@ -69,15 +83,15 @@ impl<A: Actor + Clone> Future for ConcurrentRunner<A> {
|
|||||||
.handlers
|
.handlers
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(i, (_, _, _, f))| match f.poll_unpin(cx) {
|
.filter_map(|(i, (_, _, _, _, f))| match f.poll_unpin(cx) {
|
||||||
std::task::Poll::Pending => None,
|
std::task::Poll::Pending => None,
|
||||||
std::task::Poll::Ready(res) => Some((i, res)),
|
std::task::Poll::Ready(res) => Some((i, res)),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
for (idx, res) in complete.into_iter().rev() {
|
for (idx, res) in complete.into_iter().rev() {
|
||||||
#[allow(clippy::let_underscore_future)]
|
#[allow(clippy::let_underscore_future)]
|
||||||
let (_, f, reply, _) = this.handlers.swap_remove(idx);
|
let (_, _, f, reply, _) = this.handlers.swap_remove(idx);
|
||||||
let _ = reply.send(res);
|
reply.send(res).ok();
|
||||||
// TODO: replace with Vec::extract_if once stable
|
// TODO: replace with Vec::extract_if once stable
|
||||||
if this.shutdown.is_some() {
|
if this.shutdown.is_some() {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
@@ -86,12 +100,13 @@ impl<A: Actor + Clone> Future for ConcurrentRunner<A> {
|
|||||||
&& !this
|
&& !this
|
||||||
.handlers
|
.handlers
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(_, f, _, _)| f(&*this.waiting[i].1))
|
.any(|(_, _, f, _, _)| f(&*this.waiting[i].1))
|
||||||
{
|
{
|
||||||
let (id, msg, reply) = this.waiting.remove(i);
|
let (id, msg, reply) = this.waiting.remove(i);
|
||||||
let mut actor = this.actor.clone();
|
let mut actor = this.actor.clone();
|
||||||
let queue = this.queue.clone();
|
let queue = this.queue.clone();
|
||||||
this.handlers.push((
|
this.handlers.push((
|
||||||
|
msg.type_name(),
|
||||||
id.clone(),
|
id.clone(),
|
||||||
msg.conflicts_with(),
|
msg.conflicts_with(),
|
||||||
reply,
|
reply,
|
||||||
@@ -100,6 +115,18 @@ impl<A: Actor + Clone> Future for ConcurrentRunner<A> {
|
|||||||
));
|
));
|
||||||
cont = true;
|
cont = true;
|
||||||
} else {
|
} else {
|
||||||
|
#[cfg(feature = "unstable")]
|
||||||
|
{
|
||||||
|
tracing::debug!("{} must wait...", this.waiting[i].1.type_name());
|
||||||
|
tracing::debug!(
|
||||||
|
"waiting on {:?}",
|
||||||
|
this.handlers
|
||||||
|
.iter()
|
||||||
|
.filter(|h| h.2(&*this.waiting[i].1))
|
||||||
|
.map(|h| (h.0, &h.1))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,3 +246,77 @@ impl<A: Actor + Clone> ConcurrentActor<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::rpc_continuations::Guid;
|
||||||
|
use crate::util::actor::background::BackgroundJobQueue;
|
||||||
|
use crate::util::actor::{Actor, ConflictBuilder, Handler};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct CActor;
|
||||||
|
impl Actor for CActor {
|
||||||
|
fn init(&mut self, jobs: &BackgroundJobQueue) {}
|
||||||
|
}
|
||||||
|
struct Pending;
|
||||||
|
impl Handler<Pending> for CActor {
|
||||||
|
type Response = ();
|
||||||
|
fn conflicts_with(_: &Pending) -> ConflictBuilder<Self> {
|
||||||
|
ConflictBuilder::everything().except::<NoConflicts>()
|
||||||
|
}
|
||||||
|
async fn handle(&mut self, _: Guid, _: Pending, _: &BackgroundJobQueue) -> Self::Response {
|
||||||
|
futures::future::pending().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct Conflicts;
|
||||||
|
impl Handler<Conflicts> for CActor {
|
||||||
|
type Response = ();
|
||||||
|
fn conflicts_with(_: &Conflicts) -> ConflictBuilder<Self> {
|
||||||
|
ConflictBuilder::everything().except::<NoConflicts>()
|
||||||
|
}
|
||||||
|
async fn handle(
|
||||||
|
&mut self,
|
||||||
|
_: Guid,
|
||||||
|
_: Conflicts,
|
||||||
|
_: &BackgroundJobQueue,
|
||||||
|
) -> Self::Response {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct NoConflicts;
|
||||||
|
impl Handler<NoConflicts> for CActor {
|
||||||
|
type Response = ();
|
||||||
|
fn conflicts_with(_: &NoConflicts) -> ConflictBuilder<Self> {
|
||||||
|
ConflictBuilder::nothing()
|
||||||
|
}
|
||||||
|
async fn handle(
|
||||||
|
&mut self,
|
||||||
|
_: Guid,
|
||||||
|
_: NoConflicts,
|
||||||
|
_: &BackgroundJobQueue,
|
||||||
|
) -> Self::Response {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_conflicts() {
|
||||||
|
let actor = super::ConcurrentActor::new(CActor);
|
||||||
|
let guid = Guid::new();
|
||||||
|
actor.queue(guid.clone(), Pending);
|
||||||
|
assert!(
|
||||||
|
tokio::time::timeout(Duration::from_secs(1), actor.send(Guid::new(), Conflicts))
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
tokio::time::timeout(Duration::from_secs(1), actor.send(Guid::new(), NoConflicts))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
tokio::time::timeout(Duration::from_secs(1), actor.send(guid, Conflicts))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ trait Message<A>: Send + Any {
|
|||||||
actor: &'a mut A,
|
actor: &'a mut A,
|
||||||
jobs: &'a BackgroundJobQueue,
|
jobs: &'a BackgroundJobQueue,
|
||||||
) -> BoxFuture<'a, Box<dyn Any + Send>>;
|
) -> BoxFuture<'a, Box<dyn Any + Send>>;
|
||||||
|
fn type_name(&self) -> &'static str {
|
||||||
|
std::any::type_name_of_val(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<M: Send + Any, A: Actor> Message<A> for M
|
impl<M: Send + Any, A: Actor> Message<A> for M
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -378,6 +378,7 @@ fn rollback_to_unchecked<VFrom: DynVersionT + ?Sized, VTo: DynVersionT + ?Sized>
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
pub trait VersionT
|
pub trait VersionT
|
||||||
where
|
where
|
||||||
Self: Default + Copy + Sized + RefUnwindSafe + Send + Sync + 'static,
|
Self: Default + Copy + Sized + RefUnwindSafe + Send + Sync + 'static,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use exver::{PreReleaseSegment, VersionRange};
|
|||||||
|
|
||||||
use super::v0_3_5::V0_3_0_COMPAT;
|
use super::v0_3_5::V0_3_0_COMPAT;
|
||||||
use super::{v0_4_0_alpha_4, VersionT};
|
use super::{v0_4_0_alpha_4, VersionT};
|
||||||
use crate::context::RpcContext;
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
|
|||||||
@@ -81,9 +81,13 @@ export async function checkDependencies<
|
|||||||
) {
|
) {
|
||||||
throw new Error(`Unknown HealthCheckId ${healthCheckId}`)
|
throw new Error(`Unknown HealthCheckId ${healthCheckId}`)
|
||||||
}
|
}
|
||||||
const errors = Object.entries(dep.result.healthChecks)
|
const errors =
|
||||||
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
|
dep.requirement.kind === "running"
|
||||||
.filter(([_, res]) => res.result !== "success")
|
? dep.requirement.healthChecks
|
||||||
|
.map((id) => [id, dep.result.healthChecks[id] ?? null] as const)
|
||||||
|
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
|
||||||
|
.filter(([_, res]) => res?.result !== "success")
|
||||||
|
: []
|
||||||
return errors.length === 0
|
return errors.length === 0
|
||||||
}
|
}
|
||||||
const pkgSatisfied = (packageId: DependencyId) =>
|
const pkgSatisfied = (packageId: DependencyId) =>
|
||||||
@@ -153,15 +157,20 @@ export async function checkDependencies<
|
|||||||
) {
|
) {
|
||||||
throw new Error(`Unknown HealthCheckId ${healthCheckId}`)
|
throw new Error(`Unknown HealthCheckId ${healthCheckId}`)
|
||||||
}
|
}
|
||||||
const errors = Object.entries(dep.result.healthChecks)
|
const errors =
|
||||||
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
|
dep.requirement.kind === "running"
|
||||||
.filter(([_, res]) => res.result !== "success")
|
? dep.requirement.healthChecks
|
||||||
|
.map((id) => [id, dep.result.healthChecks[id] ?? null] as const)
|
||||||
|
.filter(([id, _]) => (healthCheckId ? id === healthCheckId : true))
|
||||||
|
.filter(([_, res]) => res?.result !== "success")
|
||||||
|
: []
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
errors
|
errors
|
||||||
.map(
|
.map(([id, e]) =>
|
||||||
([_, e]) =>
|
e
|
||||||
`Health Check ${e.name} of ${dep.result.title || packageId} failed with status ${e.result}${e.message ? `: ${e.message}` : ""}`,
|
? `Health Check ${e.name} of ${dep.result.title || packageId} failed with status ${e.result}${e.message ? `: ${e.message}` : ""}`
|
||||||
|
: `Health Check ${id} of ${dep.result.title} does not exist`,
|
||||||
)
|
)
|
||||||
.join("; "),
|
.join("; "),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -134,19 +134,26 @@ export class MultiHost {
|
|||||||
const preferredExternalPort =
|
const preferredExternalPort =
|
||||||
options.preferredExternalPort ||
|
options.preferredExternalPort ||
|
||||||
knownProtocols[options.protocol].defaultPort
|
knownProtocols[options.protocol].defaultPort
|
||||||
const sslProto = this.getSslProto(options, protoInfo)
|
const sslProto = this.getSslProto(options)
|
||||||
const addSsl =
|
const addSsl = sslProto
|
||||||
sslProto && "alpn" in protoInfo
|
? {
|
||||||
|
// addXForwardedHeaders: null,
|
||||||
|
preferredExternalPort: knownProtocols[sslProto].defaultPort,
|
||||||
|
scheme: sslProto,
|
||||||
|
alpn: "alpn" in protoInfo ? protoInfo.alpn : null,
|
||||||
|
...("addSsl" in options ? options.addSsl : null),
|
||||||
|
}
|
||||||
|
: options.addSsl
|
||||||
? {
|
? {
|
||||||
// addXForwardedHeaders: null,
|
// addXForwardedHeaders: null,
|
||||||
preferredExternalPort: knownProtocols[sslProto].defaultPort,
|
preferredExternalPort: 443,
|
||||||
scheme: sslProto,
|
scheme: sslProto,
|
||||||
alpn: protoInfo.alpn,
|
alpn: null,
|
||||||
...("addSsl" in options ? options.addSsl : null),
|
...("addSsl" in options ? options.addSsl : null),
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const secure: Security | null = !protoInfo.secure ? null : { ssl: false }
|
const secure: Security | null = protoInfo.secure ?? null
|
||||||
|
|
||||||
await this.options.effects.bind({
|
await this.options.effects.bind({
|
||||||
id: this.options.id,
|
id: this.options.id,
|
||||||
@@ -159,12 +166,12 @@ export class MultiHost {
|
|||||||
return new Origin(this, internalPort, options.protocol, sslProto)
|
return new Origin(this, internalPort, options.protocol, sslProto)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSslProto(
|
private getSslProto(options: BindOptionsByKnownProtocol) {
|
||||||
options: BindOptionsByKnownProtocol,
|
const proto = options.protocol
|
||||||
protoInfo: KnownProtocols[keyof KnownProtocols],
|
const protoInfo = knownProtocols[proto]
|
||||||
) {
|
|
||||||
if (inObject("noAddSsl", options) && options.noAddSsl) return null
|
if (inObject("noAddSsl", options) && options.noAddSsl) return null
|
||||||
if ("withSsl" in protoInfo && protoInfo.withSsl) return protoInfo.withSsl
|
if ("withSsl" in protoInfo && protoInfo.withSsl) return protoInfo.withSsl
|
||||||
|
if (protoInfo.secure?.ssl) return proto
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,4 +71,30 @@ export class GetSystemSmtp {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watches the system SMTP credentials. Returns when the predicate is true
|
||||||
|
*/
|
||||||
|
async waitFor(pred: (value: T.SmtpValue | null) => boolean) {
|
||||||
|
const resolveCell = { resolve: () => {} }
|
||||||
|
this.effects.onLeaveContext(() => {
|
||||||
|
resolveCell.resolve()
|
||||||
|
})
|
||||||
|
while (this.effects.isInContext) {
|
||||||
|
let callback: () => void = () => {}
|
||||||
|
const waitForNext = new Promise<void>((resolve) => {
|
||||||
|
callback = resolve
|
||||||
|
resolveCell.resolve = resolve
|
||||||
|
})
|
||||||
|
const res = await this.effects.getSystemSmtp({
|
||||||
|
callback: () => callback(),
|
||||||
|
})
|
||||||
|
if (pred(res)) {
|
||||||
|
resolveCell.resolve()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
await waitForNext
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -366,6 +366,36 @@ export class GetServiceInterface {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watches the requested service interface. Returns when the predicate is true
|
||||||
|
*/
|
||||||
|
async waitFor(pred: (value: ServiceInterfaceFilled | null) => boolean) {
|
||||||
|
const { id, packageId } = this.opts
|
||||||
|
const resolveCell = { resolve: () => {} }
|
||||||
|
this.effects.onLeaveContext(() => {
|
||||||
|
resolveCell.resolve()
|
||||||
|
})
|
||||||
|
while (this.effects.isInContext) {
|
||||||
|
let callback: () => void = () => {}
|
||||||
|
const waitForNext = new Promise<void>((resolve) => {
|
||||||
|
callback = resolve
|
||||||
|
resolveCell.resolve = resolve
|
||||||
|
})
|
||||||
|
const res = await makeInterfaceFilled({
|
||||||
|
effects: this.effects,
|
||||||
|
id,
|
||||||
|
packageId,
|
||||||
|
callback,
|
||||||
|
})
|
||||||
|
if (pred(res)) {
|
||||||
|
resolveCell.resolve()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
await waitForNext
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export function getServiceInterface(
|
export function getServiceInterface(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
|
|||||||
@@ -130,6 +130,35 @@ export class GetServiceInterfaces {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watches the service interfaces for the package. Returns when the predicate is true
|
||||||
|
*/
|
||||||
|
async waitFor(pred: (value: ServiceInterfaceFilled[] | null) => boolean) {
|
||||||
|
const { packageId } = this.opts
|
||||||
|
const resolveCell = { resolve: () => {} }
|
||||||
|
this.effects.onLeaveContext(() => {
|
||||||
|
resolveCell.resolve()
|
||||||
|
})
|
||||||
|
while (this.effects.isInContext) {
|
||||||
|
let callback: () => void = () => {}
|
||||||
|
const waitForNext = new Promise<void>((resolve) => {
|
||||||
|
callback = resolve
|
||||||
|
resolveCell.resolve = resolve
|
||||||
|
})
|
||||||
|
const res = await makeManyInterfaceFilled({
|
||||||
|
effects: this.effects,
|
||||||
|
packageId,
|
||||||
|
callback,
|
||||||
|
})
|
||||||
|
if (pred(res)) {
|
||||||
|
resolveCell.resolve()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
await waitForNext
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export function getServiceInterfaces(
|
export function getServiceInterfaces(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
|
|||||||
@@ -249,6 +249,26 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
waitFor: async (pred: (value: string | null) => boolean) => {
|
||||||
|
const resolveCell = { resolve: () => {} }
|
||||||
|
effects.onLeaveContext(() => {
|
||||||
|
resolveCell.resolve()
|
||||||
|
})
|
||||||
|
while (effects.isInContext) {
|
||||||
|
let callback: () => void = () => {}
|
||||||
|
const waitForNext = new Promise<void>((resolve) => {
|
||||||
|
callback = resolve
|
||||||
|
resolveCell.resolve = resolve
|
||||||
|
})
|
||||||
|
const res = await effects.getContainerIp({ ...options, callback })
|
||||||
|
if (pred(res)) {
|
||||||
|
resolveCell.resolve()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
await waitForNext
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -189,6 +189,8 @@ async function runRsync(rsyncOptions: {
|
|||||||
}> {
|
}> {
|
||||||
const { srcPath, dstPath, options } = rsyncOptions
|
const { srcPath, dstPath, options } = rsyncOptions
|
||||||
|
|
||||||
|
await fs.mkdir(dstPath, { recursive: true })
|
||||||
|
|
||||||
const command = "rsync"
|
const command = "rsync"
|
||||||
const args: string[] = []
|
const args: string[] = []
|
||||||
if (options.delete) {
|
if (options.delete) {
|
||||||
|
|||||||
@@ -82,4 +82,32 @@ export class GetSslCertificate {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watches the SSL Certificate for the given hostnames if permitted. Returns when the predicate is true
|
||||||
|
*/
|
||||||
|
async waitFor(pred: (value: [string, string, string] | null) => boolean) {
|
||||||
|
const resolveCell = { resolve: () => {} }
|
||||||
|
this.effects.onLeaveContext(() => {
|
||||||
|
resolveCell.resolve()
|
||||||
|
})
|
||||||
|
while (this.effects.isInContext) {
|
||||||
|
let callback: () => void = () => {}
|
||||||
|
const waitForNext = new Promise<void>((resolve) => {
|
||||||
|
callback = resolve
|
||||||
|
resolveCell.resolve = resolve
|
||||||
|
})
|
||||||
|
const res = await this.effects.getSslCertificate({
|
||||||
|
hostnames: this.hostnames,
|
||||||
|
algorithm: this.algorithm,
|
||||||
|
callback: () => callback(),
|
||||||
|
})
|
||||||
|
if (pred(res)) {
|
||||||
|
resolveCell.resolve()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
await waitForNext
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,13 +26,17 @@ async function onCreated(path: string) {
|
|||||||
await onCreated(parent)
|
await onCreated(parent)
|
||||||
const ctrl = new AbortController()
|
const ctrl = new AbortController()
|
||||||
const watch = fs.watch(parent, { persistent: false, signal: ctrl.signal })
|
const watch = fs.watch(parent, { persistent: false, signal: ctrl.signal })
|
||||||
|
if (await exists(path)) {
|
||||||
|
ctrl.abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
await fs.access(path).then(
|
await fs.access(path).then(
|
||||||
() => true,
|
() => true,
|
||||||
() => false,
|
() => false,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
ctrl.abort("finished")
|
ctrl.abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for await (let event of watch) {
|
for await (let event of watch) {
|
||||||
@@ -100,6 +104,10 @@ type ReadType<A> = {
|
|||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
callback: (value: A | null, error?: Error) => void | Promise<void>,
|
callback: (value: A | null, error?: Error) => void | Promise<void>,
|
||||||
) => void
|
) => void
|
||||||
|
waitFor: (
|
||||||
|
effects: T.Effects,
|
||||||
|
pred: (value: A | null) => boolean,
|
||||||
|
) => Promise<A | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,7 +236,7 @@ export class FileHelper<A> {
|
|||||||
const listen = Promise.resolve()
|
const listen = Promise.resolve()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
for await (const _ of watch) {
|
for await (const _ of watch) {
|
||||||
ctrl.abort("finished")
|
ctrl.abort()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -271,6 +279,40 @@ export class FileHelper<A> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async readWaitFor<B>(
|
||||||
|
effects: T.Effects,
|
||||||
|
pred: (value: B | null, error?: Error) => boolean,
|
||||||
|
map: (value: A) => B,
|
||||||
|
): Promise<B | null> {
|
||||||
|
while (effects.isInContext) {
|
||||||
|
if (await exists(this.path)) {
|
||||||
|
const ctrl = new AbortController()
|
||||||
|
const watch = fs.watch(this.path, {
|
||||||
|
persistent: false,
|
||||||
|
signal: ctrl.signal,
|
||||||
|
})
|
||||||
|
const newRes = await this.readOnce(map)
|
||||||
|
const listen = Promise.resolve()
|
||||||
|
.then(async () => {
|
||||||
|
for await (const _ of watch) {
|
||||||
|
ctrl.abort()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(asError(e)))
|
||||||
|
if (pred(newRes)) {
|
||||||
|
ctrl.abort()
|
||||||
|
return newRes
|
||||||
|
}
|
||||||
|
await listen
|
||||||
|
} else {
|
||||||
|
if (pred(null)) return null
|
||||||
|
await onCreated(this.path).catch((e) => console.error(asError(e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
read(): ReadType<A>
|
read(): ReadType<A>
|
||||||
read<B>(
|
read<B>(
|
||||||
map: (value: A) => B,
|
map: (value: A) => B,
|
||||||
@@ -290,6 +332,8 @@ export class FileHelper<A> {
|
|||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
callback: (value: A | null, error?: Error) => void | Promise<void>,
|
callback: (value: A | null, error?: Error) => void | Promise<void>,
|
||||||
) => this.readOnChange(effects, callback, map, eq),
|
) => this.readOnChange(effects, callback, map, eq),
|
||||||
|
waitFor: (effects: T.Effects, pred: (value: A | null) => boolean) =>
|
||||||
|
this.readWaitFor(effects, pred, map),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
32
sdk/package/package-lock.json
generated
32
sdk/package/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.25",
|
"version": "0.4.0-beta.26",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.25",
|
"version": "0.4.0-beta.26",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"deep-equality-data-structures": "^2.0.0",
|
"deep-equality-data-structures": "^2.0.0",
|
||||||
"ini": "^5.0.0",
|
"ini": "^5.0.0",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"mime-types": "^3.0.1",
|
"mime": "^4.0.7",
|
||||||
"ts-matches": "^6.3.2",
|
"ts-matches": "^6.3.2",
|
||||||
"yaml": "^2.7.1"
|
"yaml": "^2.7.1"
|
||||||
},
|
},
|
||||||
@@ -3865,25 +3865,19 @@
|
|||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mime-db": {
|
"node_modules/mime": {
|
||||||
"version": "1.54.0",
|
"version": "4.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz",
|
||||||
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
"integrity": "sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa"
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"bin": {
|
||||||
"node": ">= 0.6"
|
"mime": "bin/cli.js"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mime-types": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-db": "^1.54.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mimic-fn": {
|
"node_modules/mimic-fn": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.25",
|
"version": "0.4.0-beta.26",
|
||||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||||
"main": "./package/lib/index.js",
|
"main": "./package/lib/index.js",
|
||||||
"types": "./package/lib/index.d.ts",
|
"types": "./package/lib/index.d.ts",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"homepage": "https://github.com/Start9Labs/start-sdk#readme",
|
"homepage": "https://github.com/Start9Labs/start-sdk#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"mime-types": "^3.0.1",
|
"mime": "^4.0.7",
|
||||||
"ts-matches": "^6.3.2",
|
"ts-matches": "^6.3.2",
|
||||||
"yaml": "^2.7.1",
|
"yaml": "^2.7.1",
|
||||||
"deep-equality-data-structures": "^2.0.0",
|
"deep-equality-data-structures": "^2.0.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user