addHealthCheck instead of additionalHealthChecks for Daemons (#2962)
* addHealthCheck on Daemons * fix bug that prevents domains without protocols from being deleted * fixes from testing * version bump * add sdk version to UI * fix useEntrypoint * fix dependency health check error display * minor fixes * beta.29 * fixes from testing * beta.30 * set /etc/os-release (#2918) * remove check-monitor from kiosk (#2059) * add units for progress (#2693) * use new progress type * alpha.7 * fix up pwa stuff * fix wormhole-squashfs and prune boot (#2964) * don't exit on expected errors * use bash --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
8
Makefile
@@ -179,7 +179,7 @@ wormhole-squashfs: results/$(BASENAME).squashfs
|
|||||||
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
|
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
|
||||||
@echo "Paste the following command into the shell of your StartOS server:"
|
@echo "Paste the following command into the shell of your StartOS server:"
|
||||||
@echo
|
@echo
|
||||||
@wormhole send results/$(BASENAME).squashfs 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo sh -c '"'"'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE) && cd /media/startos/images && wormhole receive --accept-file %s && mv $(BASENAME).squashfs $(SQFS_SUM).rootfs && ln -rsf ./$(SQFS_SUM).rootfs ../config/current.rootfs && sync && reboot'"'"'\n", $$3 }'
|
@wormhole send results/$(BASENAME).squashfs 2>&1 | awk -Winteractive '/wormhole receive/ { printf "sudo sh -c '"'"'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE) && /usr/lib/startos/scripts/prune-boot && cd /media/startos/images && wormhole receive --accept-file %s && CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/use-img ./$(BASENAME).squashfs'"'"'\n", $$3 }'
|
||||||
|
|
||||||
update: $(ALL_TARGETS)
|
update: $(ALL_TARGETS)
|
||||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||||
@@ -205,9 +205,9 @@ update-squashfs: results/$(BASENAME).squashfs
|
|||||||
$(eval SQFS_SUM := $(shell b3sum results/$(BASENAME).squashfs))
|
$(eval SQFS_SUM := $(shell b3sum results/$(BASENAME).squashfs))
|
||||||
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
|
$(eval SQFS_SIZE := $(shell du -s --bytes results/$(BASENAME).squashfs | awk '{print $$1}'))
|
||||||
$(call ssh,'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE)')
|
$(call ssh,'/usr/lib/startos/scripts/prune-images $(SQFS_SIZE)')
|
||||||
$(call cp,results/$(BASENAME).squashfs,/media/startos/images/$(SQFS_SUM).rootfs)
|
$(call ssh,'/usr/lib/startos/scripts/prune-boot')
|
||||||
$(call ssh,'sudo ln -rsf /media/startos/images/$(SQFS_SUM).rootfs /media/startos/config/current.rootfs')
|
$(call cp,results/$(BASENAME).squashfs,/media/startos/images/next.rootfs)
|
||||||
$(call ssh,'sudo reboot')
|
$(call ssh,'sudo CHECKSUM=$(SQFS_SUM) /usr/lib/startos/scripts/use-img /media/startos/images/next.rootfs')
|
||||||
|
|
||||||
emulate-reflash: $(ALL_TARGETS)
|
emulate-reflash: $(ALL_TARGETS)
|
||||||
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
@if [ -z "$(REMOTE)" ]; then >&2 echo "Must specify REMOTE" && false; fi
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
|
|
||||||
if cat /sys/class/drm/*/status | grep -qw connected; then
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -91,15 +91,6 @@ cat > /home/kiosk/kiosk.sh << 'EOF'
|
|||||||
while ! curl "http://localhost" > /dev/null; do
|
while ! curl "http://localhost" > /dev/null; do
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
while ! /usr/lib/startos/scripts/check-monitor; do
|
|
||||||
sleep 15
|
|
||||||
done
|
|
||||||
(
|
|
||||||
while /usr/lib/startos/scripts/check-monitor; do
|
|
||||||
sleep 15
|
|
||||||
done
|
|
||||||
killall firefox-esr
|
|
||||||
) &
|
|
||||||
matchbox-window-manager -use_titlebar no &
|
matchbox-window-manager -use_titlebar no &
|
||||||
cp -r /home/kiosk/fx-profile /home/kiosk/fx-profile-tmp
|
cp -r /home/kiosk/fx-profile /home/kiosk/fx-profile-tmp
|
||||||
firefox-esr http://localhost --profile /home/kiosk/fx-profile-tmp
|
firefox-esr http://localhost --profile /home/kiosk/fx-profile-tmp
|
||||||
|
|||||||
35
build/lib/scripts/prune-boot
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$UID" -ne 0 ]; then
|
||||||
|
>&2 echo 'Must be run as root'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the current kernel version
|
||||||
|
current_kernel=$(uname -r)
|
||||||
|
|
||||||
|
echo "Current kernel: $current_kernel"
|
||||||
|
echo "Searching for old kernel files in /boot..."
|
||||||
|
|
||||||
|
# Extract base kernel version (without possible suffixes)
|
||||||
|
current_base=$(echo "$current_kernel" | sed 's/-.*//')
|
||||||
|
|
||||||
|
cd /boot || { echo "/boot directory not found!"; exit 1; }
|
||||||
|
|
||||||
|
for file in vmlinuz-* initrd.img-* System.map-* config-*; do
|
||||||
|
# Extract version from filename
|
||||||
|
version=$(echo "$file" | sed -E 's/^[^0-9]*([0-9][^ ]*).*/\1/')
|
||||||
|
# Skip if file matches current kernel version
|
||||||
|
if [[ "$file" == *"$current_kernel"* ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Compare versions, delete if less than current
|
||||||
|
if dpkg --compare-versions "$version" lt "$current_kernel"; then
|
||||||
|
echo "Deleting $file (version $version is older than $current_kernel)"
|
||||||
|
sudo rm -f "$file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Old kernel files deleted."
|
||||||
61
build/lib/scripts/use-img
Executable file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$UID" -ne 0 ]; then
|
||||||
|
>&2 echo 'Must be run as root'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
>&2 echo "usage: $0 <SQUASHFS>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION=$(unsquashfs -cat $1 /usr/lib/startos/VERSION.txt)
|
||||||
|
GIT_HASH=$(unsquashfs -cat $1 /usr/lib/startos/GIT_HASH.txt)
|
||||||
|
B3SUM=$(b3sum $1 | head -c 32)
|
||||||
|
|
||||||
|
if [ -n "$CHECKSUM" ] && [ "$CHECKSUM" != "$B3SUM" ]; then
|
||||||
|
>&2 echo "CHECKSUM MISMATCH"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv $1 /media/startos/images/${B3SUM}.rootfs
|
||||||
|
ln -rsf /media/startos/images/${B3SUM}.rootfs /media/startos/config/current.rootfs
|
||||||
|
|
||||||
|
unsquashfs -n -f -d / /media/startos/images/${B3SUM}.rootfs boot
|
||||||
|
|
||||||
|
umount -R /media/startos/next 2> /dev/null || true
|
||||||
|
umount -R /media/startos/lower 2> /dev/null || true
|
||||||
|
umount -R /media/startos/upper 2> /dev/null || true
|
||||||
|
|
||||||
|
rm -rf /media/startos/lower /media/startos/upper /media/startos/next
|
||||||
|
mkdir /media/startos/upper
|
||||||
|
mount -t tmpfs tmpfs /media/startos/upper
|
||||||
|
mkdir -p /media/startos/lower /media/startos/upper/data /media/startos/upper/work /media/startos/next
|
||||||
|
mount /media/startos/images/${B3SUM}.rootfs /media/startos/lower
|
||||||
|
mount -t overlay \
|
||||||
|
-olowerdir=/media/startos/lower,upperdir=/media/startos/upper/data,workdir=/media/startos/upper/work \
|
||||||
|
overlay /media/startos/next
|
||||||
|
mkdir -p /media/startos/next/media/startos/root
|
||||||
|
mount --bind /media/startos/root /media/startos/next/media/startos/root
|
||||||
|
mkdir -p /media/startos/next/dev
|
||||||
|
mkdir -p /media/startos/next/sys
|
||||||
|
mkdir -p /media/startos/next/proc
|
||||||
|
mkdir -p /media/startos/next/boot
|
||||||
|
mount --bind /dev /media/startos/next/dev
|
||||||
|
mount --bind /sys /media/startos/next/sys
|
||||||
|
mount --bind /proc /media/startos/next/proc
|
||||||
|
mount --bind /boot /media/startos/next/boot
|
||||||
|
|
||||||
|
chroot /media/startos/next update-grub2
|
||||||
|
|
||||||
|
umount -R /media/startos/next
|
||||||
|
umount -R /media/startos/upper
|
||||||
|
umount -R /media/startos/lower
|
||||||
|
rm -rf /media/startos/lower /media/startos/upper /media/startos/next
|
||||||
|
|
||||||
|
sync
|
||||||
|
|
||||||
|
reboot
|
||||||
2
container-runtime/package-lock.json
generated
@@ -38,7 +38,7 @@
|
|||||||
},
|
},
|
||||||
"../sdk/dist": {
|
"../sdk/dist": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.27",
|
"version": "0.4.0-beta.30",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ export class MainLoop {
|
|||||||
private subcontainerRc?: SubContainerRc<SDKManifest>
|
private subcontainerRc?: SubContainerRc<SDKManifest>
|
||||||
get mainSubContainerHandle() {
|
get mainSubContainerHandle() {
|
||||||
this.subcontainerRc =
|
this.subcontainerRc =
|
||||||
this.subcontainerRc ?? this.mainEvent?.daemon?.subcontainerRc()
|
this.subcontainerRc ??
|
||||||
|
this.mainEvent?.daemon?.subcontainerRc() ??
|
||||||
|
undefined
|
||||||
return this.subcontainerRc
|
return this.subcontainerRc
|
||||||
}
|
}
|
||||||
private healthLoops?: {
|
private healthLoops?: {
|
||||||
|
|||||||
2
core/Cargo.lock
generated
@@ -5975,7 +5975,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "start-os"
|
name = "start-os"
|
||||||
version = "0.4.0-alpha.6"
|
version = "0.4.0-alpha.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes 0.7.5",
|
"aes 0.7.5",
|
||||||
"async-acme",
|
"async-acme",
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use color_eyre::eyre::bail;
|
|
||||||
use container_init::{Input, Output, ProcessId, RpcId};
|
|
||||||
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
/// Used by the js-executor, it is the ability to just create a command in an already running exec
|
|
||||||
pub type ExecCommand = Arc<
|
|
||||||
dyn Fn(
|
|
||||||
String,
|
|
||||||
Vec<String>,
|
|
||||||
UnboundedSender<container_init::Output>,
|
|
||||||
Option<Duration>,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<RpcId, String>> + 'static>>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
>;
|
|
||||||
|
|
||||||
/// Used by the js-executor, it is the ability to just create a command in an already running exec
|
|
||||||
pub type SendKillSignal = Arc<
|
|
||||||
dyn Fn(RpcId, u32) -> Pin<Box<dyn Future<Output = Result<(), String>> + 'static>>
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ 'static,
|
|
||||||
>;
|
|
||||||
|
|
||||||
pub trait CommandInserter {
|
|
||||||
fn insert_command(
|
|
||||||
&self,
|
|
||||||
command: String,
|
|
||||||
args: Vec<String>,
|
|
||||||
sender: UnboundedSender<container_init::Output>,
|
|
||||||
timeout: Option<Duration>,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Option<RpcId>>>>;
|
|
||||||
|
|
||||||
fn send_signal(&self, id: RpcId, command: u32) -> Pin<Box<dyn Future<Output = ()>>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ArcCommandInserter = Arc<Mutex<Option<Box<dyn CommandInserter>>>>;
|
|
||||||
|
|
||||||
pub struct ExecutingCommand {
|
|
||||||
rpc_id: RpcId,
|
|
||||||
/// Will exist until killed
|
|
||||||
command_inserter: Arc<Mutex<Option<ArcCommandInserter>>>,
|
|
||||||
owned_futures: Arc<Mutex<Vec<Pin<Box<dyn Future<Output = ()>>>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExecutingCommand {
|
|
||||||
pub async fn new(
|
|
||||||
command_inserter: ArcCommandInserter,
|
|
||||||
command: String,
|
|
||||||
args: Vec<String>,
|
|
||||||
timeout: Option<Duration>,
|
|
||||||
) -> Result<ExecutingCommand, color_eyre::Report> {
|
|
||||||
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel::<Output>();
|
|
||||||
let rpc_id = {
|
|
||||||
let locked_command_inserter = command_inserter.lock().await;
|
|
||||||
let locked_command_inserter = match &*locked_command_inserter {
|
|
||||||
Some(a) => a,
|
|
||||||
None => bail!("Expecting containers.main in the package manifest".to_string()),
|
|
||||||
};
|
|
||||||
match locked_command_inserter
|
|
||||||
.insert_command(command, args, sender, timeout)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Some(a) => a,
|
|
||||||
None => bail!("Couldn't get command started ".to_string()),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let executing_commands = ExecutingCommand {
|
|
||||||
rpc_id,
|
|
||||||
command_inserter: Arc::new(Mutex::new(Some(command_inserter.clone()))),
|
|
||||||
owned_futures: Default::default(),
|
|
||||||
};
|
|
||||||
// let waiting = self.wait()
|
|
||||||
Ok(executing_commands)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn wait(
|
|
||||||
rpc_id: RpcId,
|
|
||||||
mut outputs: UnboundedReceiver<Output>,
|
|
||||||
) -> Result<String, (Option<i32>, String)> {
|
|
||||||
let (process_id_send, process_id_recv) = tokio::sync::oneshot::channel::<ProcessId>();
|
|
||||||
let mut answer = String::new();
|
|
||||||
let mut command_error = String::new();
|
|
||||||
let mut status: Option<i32> = None;
|
|
||||||
let mut process_id_send = Some(process_id_send);
|
|
||||||
while let Some(output) = outputs.recv().await {
|
|
||||||
match output {
|
|
||||||
Output::ProcessId(process_id) => {
|
|
||||||
if let Some(process_id_send) = process_id_send.take() {
|
|
||||||
if let Err(err) = process_id_send.send(process_id) {
|
|
||||||
tracing::error!(
|
|
||||||
"Could not get a process id {process_id:?} sent for {rpc_id:?}"
|
|
||||||
);
|
|
||||||
tracing::debug!("{err:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Output::Line(value) => {
|
|
||||||
answer.push_str(&value);
|
|
||||||
answer.push('\n');
|
|
||||||
}
|
|
||||||
Output::Error(error) => {
|
|
||||||
command_error.push_str(&error);
|
|
||||||
command_error.push('\n');
|
|
||||||
}
|
|
||||||
Output::Done(error_code) => {
|
|
||||||
status = error_code;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !command_error.is_empty() {
|
|
||||||
return Err((status, command_error));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(answer)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_signal(&self, signal: u32) {
|
|
||||||
let locked = self.command_inserter.lock().await;
|
|
||||||
let inner = match &*locked {
|
|
||||||
Some(a) => a,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
let locked = inner.lock().await;
|
|
||||||
let command_inserter = match &*locked {
|
|
||||||
Some(a) => a,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
command_inserter.send_signal(self.rpc_id, signal);
|
|
||||||
}
|
|
||||||
/// Should only be called when output::done
|
|
||||||
async fn killed(&self) {
|
|
||||||
*self.owned_futures.lock().await = Default::default();
|
|
||||||
*self.command_inserter.lock().await = Default::default();
|
|
||||||
}
|
|
||||||
pub fn rpc_id(&self) -> RpcId {
|
|
||||||
self.rpc_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for ExecutingCommand {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let command_inserter = self.command_inserter.clone();
|
|
||||||
let rpc_id = self.rpc_id.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let command_inserter_lock = command_inserter.lock().await;
|
|
||||||
let command_inserter = match &*command_inserter_lock {
|
|
||||||
Some(a) => a,
|
|
||||||
None => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
command_inserter.send_kill_command(rpc_id, 9).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,7 @@ keywords = [
|
|||||||
name = "start-os"
|
name = "start-os"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/Start9Labs/start-os"
|
repository = "https://github.com/Start9Labs/start-os"
|
||||||
version = "0.4.0-alpha.6" # VERSION_BUMP
|
version = "0.4.0-alpha.7" # VERSION_BUMP
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|||||||
@@ -252,14 +252,18 @@ impl fmt::Display for ActionResultV1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_action_result<T: Serialize>(params: WithIoFormat<T>, result: Option<ActionResult>) {
|
pub fn display_action_result<T: Serialize>(
|
||||||
|
params: WithIoFormat<T>,
|
||||||
|
result: Option<ActionResult>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let Some(result) = result else {
|
let Some(result) = result else {
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
return display_serializable(format, result);
|
return display_serializable(format, result);
|
||||||
}
|
}
|
||||||
println!("{result}")
|
println!("{result}");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, TS)]
|
#[derive(Deserialize, Serialize, TS)]
|
||||||
|
|||||||
@@ -328,9 +328,7 @@ pub fn session<C: Context>() -> ParentHandler<C> {
|
|||||||
from_fn_async(list)
|
from_fn_async(list)
|
||||||
.with_metadata("get_session", Value::Bool(true))
|
.with_metadata("get_session", Value::Bool(true))
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| {
|
.with_custom_display_fn(|handle, result| display_sessions(handle.params, result))
|
||||||
Ok(display_sessions(handle.params, result))
|
|
||||||
})
|
|
||||||
.with_about("Display all server sessions")
|
.with_about("Display all server sessions")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
@@ -343,7 +341,7 @@ pub fn session<C: Context>() -> ParentHandler<C> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_sessions(params: WithIoFormat<ListParams>, arg: SessionList) {
|
fn display_sessions(params: WithIoFormat<ListParams>, arg: SessionList) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -371,7 +369,8 @@ fn display_sessions(params: WithIoFormat<ListParams>, arg: SessionList) {
|
|||||||
}
|
}
|
||||||
table.add_row(row);
|
table.add_row(row);
|
||||||
}
|
}
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use crate::disk::mount::filesystem::ReadWrite;
|
|||||||
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, TmpMountGuard};
|
||||||
use crate::init::init;
|
use crate::init::init;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::progress::ProgressUnits;
|
||||||
use crate::s9pk::S9pk;
|
use crate::s9pk::S9pk;
|
||||||
use crate::service::service_map::DownloadInstallFuture;
|
use crate::service::service_map::DownloadInstallFuture;
|
||||||
use crate::setup::SetupExecuteProgress;
|
use crate::setup::SetupExecuteProgress;
|
||||||
@@ -136,6 +137,7 @@ pub async fn recover_full_embassy(
|
|||||||
.collect();
|
.collect();
|
||||||
let tasks = restore_packages(&rpc_ctx, backup_guard, ids).await?;
|
let tasks = restore_packages(&rpc_ctx, backup_guard, ids).await?;
|
||||||
restore_phase.set_total(tasks.len() as u64);
|
restore_phase.set_total(tasks.len() as u64);
|
||||||
|
restore_phase.set_units(Some(ProgressUnits::Steps));
|
||||||
let restore_phase = Arc::new(Mutex::new(restore_phase));
|
let restore_phase = Arc::new(Mutex::new(restore_phase));
|
||||||
stream::iter(tasks)
|
stream::iter(tasks)
|
||||||
.for_each_concurrent(5, |(id, res)| {
|
.for_each_concurrent(5, |(id, res)| {
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ pub fn target<C: Context>() -> ParentHandler<C> {
|
|||||||
from_fn_async(info)
|
from_fn_async(info)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn::<CliContext, _>(|params, info| {
|
.with_custom_display_fn::<CliContext, _>(|params, info| {
|
||||||
Ok(display_backup_info(params.params, info))
|
display_backup_info(params.params, info)
|
||||||
})
|
})
|
||||||
.with_about("Display package backup information")
|
.with_about("Display package backup information")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
@@ -227,7 +227,7 @@ pub struct PackageBackupInfo {
|
|||||||
pub timestamp: DateTime<Utc>,
|
pub timestamp: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) {
|
fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -260,7 +260,8 @@ fn display_backup_info(params: WithIoFormat<InfoParams>, info: BackupInfo) {
|
|||||||
];
|
];
|
||||||
table.add_row(row);
|
table.add_row(row);
|
||||||
}
|
}
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
|
|||||||
@@ -48,9 +48,7 @@ pub fn disk<C: Context>() -> ParentHandler<C> {
|
|||||||
"list",
|
"list",
|
||||||
from_fn_async(list)
|
from_fn_async(list)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| {
|
.with_custom_display_fn(|handle, result| display_disk_info(handle.params, result))
|
||||||
Ok(display_disk_info(handle.params, result))
|
|
||||||
})
|
|
||||||
.with_about("List disk info")
|
.with_about("List disk info")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
@@ -65,7 +63,7 @@ pub fn disk<C: Context>() -> ParentHandler<C> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_disk_info(params: WithIoFormat<Empty>, args: Vec<DiskInfo>) {
|
fn display_disk_info(params: WithIoFormat<Empty>, args: Vec<DiskInfo>) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -124,7 +122,8 @@ fn display_disk_info(params: WithIoFormat<Empty>, args: Vec<DiskInfo>) {
|
|||||||
table.add_row(row);
|
table.add_row(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[command(display(display_disk_info))]
|
// #[command(display(display_disk_info))]
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ use crate::net::utils::find_wifi_iface;
|
|||||||
use crate::net::web_server::{UpgradableListener, WebServerAcceptorSetter};
|
use crate::net::web_server::{UpgradableListener, WebServerAcceptorSetter};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{
|
use crate::progress::{
|
||||||
FullProgress, FullProgressTracker, PhaseProgressTrackerHandle, PhasedProgressBar,
|
FullProgress, FullProgressTracker, PhaseProgressTrackerHandle, PhasedProgressBar, ProgressUnits,
|
||||||
};
|
};
|
||||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||||
use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL};
|
use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL};
|
||||||
@@ -259,6 +259,7 @@ pub async fn run_script<P: AsRef<Path>>(path: P, mut progress: PhaseProgressTrac
|
|||||||
if let Err(e) = async {
|
if let Err(e) = async {
|
||||||
let script = tokio::fs::read_to_string(script).await?;
|
let script = tokio::fs::read_to_string(script).await?;
|
||||||
progress.set_total(script.as_bytes().iter().filter(|b| **b == b'\n').count() as u64);
|
progress.set_total(script.as_bytes().iter().filter(|b| **b == b'\n').count() as u64);
|
||||||
|
progress.set_units(Some(ProgressUnits::Bytes));
|
||||||
let mut reader = IOHook::new(Cursor::new(script.as_bytes()));
|
let mut reader = IOHook::new(Cursor::new(script.as_bytes()));
|
||||||
reader.post_read(|buf| progress += buf.iter().filter(|b| **b == b'\n').count() as u64);
|
reader.post_read(|buf| progress += buf.iter().filter(|b| **b == b'\n').count() as u64);
|
||||||
Command::new("/bin/bash")
|
Command::new("/bin/bash")
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ use crate::context::{
|
|||||||
use crate::disk::fsck::RequiresReboot;
|
use crate::disk::fsck::RequiresReboot;
|
||||||
use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
||||||
use crate::system::kiosk;
|
use crate::system::kiosk;
|
||||||
use crate::util::serde::{HandlerExtSerde, WithIoFormat};
|
use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -201,15 +201,6 @@ pub fn main_api<C: Context>() -> ParentHandler<C> {
|
|||||||
if &*PLATFORM != "raspberrypi" {
|
if &*PLATFORM != "raspberrypi" {
|
||||||
api = api.subcommand("kiosk", kiosk::<C>());
|
api = api.subcommand("kiosk", kiosk::<C>());
|
||||||
}
|
}
|
||||||
#[cfg(feature = "dev")]
|
|
||||||
{
|
|
||||||
api = api.subcommand(
|
|
||||||
"lxc",
|
|
||||||
lxc::dev::lxc::<C>().with_about(
|
|
||||||
"Commands related to lxc containers i.e. create, list, remove, connect",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
api
|
api
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +211,7 @@ pub fn server<C: Context>() -> ParentHandler<C> {
|
|||||||
from_fn_async(system::time)
|
from_fn_async(system::time)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| {
|
.with_custom_display_fn(|handle, result| {
|
||||||
Ok(system::display_time(handle.params, result))
|
system::display_time(handle.params, result)
|
||||||
})
|
})
|
||||||
.with_about("Display current time and server uptime")
|
.with_about("Display current time and server uptime")
|
||||||
.with_call_remote::<CliContext>()
|
.with_call_remote::<CliContext>()
|
||||||
@@ -416,6 +407,46 @@ pub fn package<C: Context>() -> ParentHandler<C> {
|
|||||||
.with_about("Rebuild service container")
|
.with_about("Rebuild service container")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"stats",
|
||||||
|
from_fn_async(lxc::stats)
|
||||||
|
.with_display_serializable()
|
||||||
|
.with_custom_display_fn(|args, res| {
|
||||||
|
if let Some(format) = args.params.format {
|
||||||
|
return display_serializable(format, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
use prettytable::*;
|
||||||
|
let mut table = table!([
|
||||||
|
"Name",
|
||||||
|
"Container ID",
|
||||||
|
"Memory Usage",
|
||||||
|
"Memory Limit",
|
||||||
|
"Memory %"
|
||||||
|
]);
|
||||||
|
for (id, stats) in res {
|
||||||
|
if let Some(stats) = stats {
|
||||||
|
table.add_row(row![
|
||||||
|
&*id,
|
||||||
|
&*stats.container_id,
|
||||||
|
stats.memory_usage,
|
||||||
|
stats.memory_limit,
|
||||||
|
format!(
|
||||||
|
"{:.2}",
|
||||||
|
stats.memory_usage.0 as f64 / stats.memory_limit.0 as f64
|
||||||
|
* 100.0
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
table.add_row(row![&*id, "N/A", "0 MiB", "0 MiB", "0"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.with_about("List information related to the lxc containers i.e. CPU, Memory, Disk")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
.subcommand("logs", logs::package_logs())
|
.subcommand("logs", logs::package_logs())
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"logs",
|
"logs",
|
||||||
|
|||||||
@@ -1,174 +0,0 @@
|
|||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use rpc_toolkit::{
|
|
||||||
from_fn_async, CallRemoteHandler, Context, Empty, HandlerArgs, HandlerExt, HandlerFor,
|
|
||||||
ParentHandler,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use ts_rs::TS;
|
|
||||||
|
|
||||||
use crate::context::{CliContext, RpcContext};
|
|
||||||
use crate::lxc::{ContainerId, LxcConfig};
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::rpc_continuations::Guid;
|
|
||||||
use crate::service::ServiceStats;
|
|
||||||
|
|
||||||
pub fn lxc<C: Context>() -> ParentHandler<C> {
|
|
||||||
ParentHandler::new()
|
|
||||||
.subcommand(
|
|
||||||
"create",
|
|
||||||
from_fn_async(create)
|
|
||||||
.with_about("Create lxc container")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"list",
|
|
||||||
from_fn_async(list)
|
|
||||||
.with_custom_display_fn(|_, res| {
|
|
||||||
use prettytable::*;
|
|
||||||
let mut table = table!([bc => "GUID"]);
|
|
||||||
for guid in res {
|
|
||||||
table.add_row(row![&*guid]);
|
|
||||||
}
|
|
||||||
table.printstd();
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.with_about("List lxc containers")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"stats",
|
|
||||||
from_fn_async(stats)
|
|
||||||
.with_custom_display_fn(|_, res| {
|
|
||||||
use prettytable::*;
|
|
||||||
let mut table = table!([
|
|
||||||
"Container ID",
|
|
||||||
"Name",
|
|
||||||
"Memory Usage",
|
|
||||||
"Memory Limit",
|
|
||||||
"Memory %"
|
|
||||||
]);
|
|
||||||
for ServiceStats {
|
|
||||||
container_id,
|
|
||||||
package_id,
|
|
||||||
memory_usage,
|
|
||||||
memory_limit,
|
|
||||||
} in res
|
|
||||||
{
|
|
||||||
table.add_row(row![
|
|
||||||
&*container_id,
|
|
||||||
&*package_id,
|
|
||||||
memory_usage,
|
|
||||||
memory_limit,
|
|
||||||
format!(
|
|
||||||
"{:.2}",
|
|
||||||
memory_usage.0 as f64 / memory_limit.0 as f64 * 100.0
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
table.printstd();
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.with_about("List information related to the lxc containers i.e. CPU, Memory, Disk")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
"remove",
|
|
||||||
from_fn_async(remove)
|
|
||||||
.no_display()
|
|
||||||
.with_about("Remove lxc container")
|
|
||||||
.with_call_remote::<CliContext>(),
|
|
||||||
)
|
|
||||||
.subcommand("connect", from_fn_async(connect_rpc).no_cli())
|
|
||||||
.subcommand(
|
|
||||||
"connect",
|
|
||||||
from_fn_async(connect_rpc_cli)
|
|
||||||
.no_display()
|
|
||||||
.with_about("Connect to a lxc container"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create(ctx: RpcContext) -> Result<ContainerId, Error> {
|
|
||||||
let container = ctx.lxc_manager.create(None, LxcConfig::default()).await?;
|
|
||||||
let guid = container.guid.deref().clone();
|
|
||||||
ctx.dev.lxc.lock().await.insert(guid.clone(), container);
|
|
||||||
Ok(guid)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list(ctx: RpcContext) -> Result<Vec<ContainerId>, Error> {
|
|
||||||
Ok(ctx.dev.lxc.lock().await.keys().cloned().collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn stats(ctx: RpcContext) -> Result<Vec<ServiceStats>, Error> {
|
|
||||||
let ids = ctx.db.peek().await.as_public().as_package_data().keys()?;
|
|
||||||
let guids: Vec<_> = ctx.dev.lxc.lock().await.keys().cloned().collect();
|
|
||||||
|
|
||||||
let mut stats = Vec::with_capacity(guids.len());
|
|
||||||
for id in ids {
|
|
||||||
let service: tokio::sync::OwnedRwLockReadGuard<Option<crate::service::ServiceRef>> =
|
|
||||||
ctx.services.get(&id).await;
|
|
||||||
|
|
||||||
let service_ref = service.as_ref().or_not_found(&id)?;
|
|
||||||
|
|
||||||
stats.push(service_ref.stats().await?);
|
|
||||||
}
|
|
||||||
Ok(stats)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
|
||||||
pub struct RemoveParams {
|
|
||||||
#[ts(type = "string")]
|
|
||||||
pub guid: ContainerId,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn remove(ctx: RpcContext, RemoveParams { guid }: RemoveParams) -> Result<(), Error> {
|
|
||||||
if let Some(container) = ctx.dev.lxc.lock().await.remove(&guid) {
|
|
||||||
container.exit().await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
|
||||||
pub struct ConnectParams {
|
|
||||||
#[ts(type = "string")]
|
|
||||||
pub guid: ContainerId,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect_rpc(
|
|
||||||
ctx: RpcContext,
|
|
||||||
ConnectParams { guid }: ConnectParams,
|
|
||||||
) -> Result<Guid, Error> {
|
|
||||||
super::connect(
|
|
||||||
&ctx,
|
|
||||||
ctx.dev.lxc.lock().await.get(&guid).ok_or_else(|| {
|
|
||||||
Error::new(eyre!("No container with guid: {guid}"), ErrorKind::NotFound)
|
|
||||||
})?,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect_rpc_cli(
|
|
||||||
HandlerArgs {
|
|
||||||
context,
|
|
||||||
parent_method,
|
|
||||||
method,
|
|
||||||
params,
|
|
||||||
inherited_params,
|
|
||||||
raw_params,
|
|
||||||
}: HandlerArgs<CliContext, ConnectParams>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let ctx = context.clone();
|
|
||||||
let guid = CallRemoteHandler::<CliContext, _, _>::new(from_fn_async(connect_rpc))
|
|
||||||
.handle_async(HandlerArgs {
|
|
||||||
context,
|
|
||||||
parent_method,
|
|
||||||
method,
|
|
||||||
params: rpc_toolkit::util::Flat(params, Empty {}),
|
|
||||||
inherited_params,
|
|
||||||
raw_params,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
super::connect_cli(&ctx, guid).await
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
@@ -7,7 +7,7 @@ use std::time::Duration;
|
|||||||
use clap::builder::ValueParserFactory;
|
use clap::builder::ValueParserFactory;
|
||||||
use futures::{AsyncWriteExt, StreamExt};
|
use futures::{AsyncWriteExt, StreamExt};
|
||||||
use imbl_value::{InOMap, InternedString};
|
use imbl_value::{InOMap, InternedString};
|
||||||
use models::{FromStrParser, InvalidId};
|
use models::{FromStrParser, InvalidId, PackageId};
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use rpc_toolkit::{GenericRpcMethod, RpcRequest, RpcResponse};
|
use rpc_toolkit::{GenericRpcMethod, RpcRequest, RpcResponse};
|
||||||
use rustyline_async::{ReadlineEvent, SharedWriter};
|
use rustyline_async::{ReadlineEvent, SharedWriter};
|
||||||
@@ -28,13 +28,11 @@ use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard};
|
|||||||
use crate::disk::mount::util::unmount;
|
use crate::disk::mount::util::unmount;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||||
|
use crate::service::ServiceStats;
|
||||||
use crate::util::io::open_file;
|
use crate::util::io::open_file;
|
||||||
use crate::util::rpc_client::UnixRpcClient;
|
use crate::util::rpc_client::UnixRpcClient;
|
||||||
use crate::util::{new_guid, Invoke};
|
use crate::util::{new_guid, Invoke};
|
||||||
|
|
||||||
// #[cfg(feature = "dev")]
|
|
||||||
pub mod dev;
|
|
||||||
|
|
||||||
const LXC_CONTAINER_DIR: &str = "/var/lib/lxc";
|
const LXC_CONTAINER_DIR: &str = "/var/lib/lxc";
|
||||||
const RPC_DIR: &str = "media/startos/rpc"; // must not be absolute path
|
const RPC_DIR: &str = "media/startos/rpc"; // must not be absolute path
|
||||||
pub const CONTAINER_RPC_SERVER_SOCKET: &str = "service.sock"; // must not be absolute path
|
pub const CONTAINER_RPC_SERVER_SOCKET: &str = "service.sock"; // must not be absolute path
|
||||||
@@ -564,3 +562,21 @@ pub async fn connect_cli(ctx: &CliContext, guid: Guid) -> Result<(), Error> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn stats(ctx: RpcContext) -> Result<BTreeMap<PackageId, Option<ServiceStats>>, Error> {
|
||||||
|
let ids = ctx.db.peek().await.as_public().as_package_data().keys()?;
|
||||||
|
|
||||||
|
let mut stats = BTreeMap::new();
|
||||||
|
for id in ids {
|
||||||
|
let service: tokio::sync::OwnedRwLockReadGuard<Option<crate::service::ServiceRef>> =
|
||||||
|
ctx.services.get(&id).await;
|
||||||
|
|
||||||
|
let Some(service_ref) = service.as_ref() else {
|
||||||
|
stats.insert(id, None);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
stats.insert(id, Some(service_ref.stats().await?));
|
||||||
|
}
|
||||||
|
Ok(stats)
|
||||||
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ pub fn binding<C: Context, Kind: HostApiKind>(
|
|||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
return Ok(display_serializable(format, res));
|
return display_serializable(format, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
@@ -182,7 +182,7 @@ pub fn binding<C: Context, Kind: HostApiKind>(
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ pub fn network_interface_api<C: Context>() -> ParentHandler<C> {
|
|||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
return Ok(display_serializable(format, res));
|
return display_serializable(format, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
@@ -78,7 +78,7 @@ pub fn network_interface_api<C: Context>() -> ParentHandler<C> {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -90,9 +90,7 @@ pub fn tor<C: Context>() -> ParentHandler<C> {
|
|||||||
"list-services",
|
"list-services",
|
||||||
from_fn_async(list_services)
|
from_fn_async(list_services)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| {
|
.with_custom_display_fn(|handle, result| display_services(handle.params, result))
|
||||||
Ok(display_services(handle.params, result))
|
|
||||||
})
|
|
||||||
.with_about("Display Tor V3 Onion Addresses")
|
.with_about("Display Tor V3 Onion Addresses")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
@@ -210,7 +208,10 @@ pub async fn reset(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_services(params: WithIoFormat<Empty>, services: Vec<OnionAddressV3>) {
|
pub fn display_services(
|
||||||
|
params: WithIoFormat<Empty>,
|
||||||
|
services: Vec<OnionAddressV3>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -222,7 +223,8 @@ pub fn display_services(params: WithIoFormat<Empty>, services: Vec<OnionAddressV
|
|||||||
let row = row![&service.to_string()];
|
let row = row![&service.to_string()];
|
||||||
table.add_row(row);
|
table.add_row(row);
|
||||||
}
|
}
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_services(ctx: RpcContext, _: Empty) -> Result<Vec<OnionAddressV3>, Error> {
|
pub async fn list_services(ctx: RpcContext, _: Empty) -> Result<Vec<OnionAddressV3>, Error> {
|
||||||
|
|||||||
@@ -70,9 +70,7 @@ pub fn wifi<C: Context>() -> ParentHandler<C> {
|
|||||||
"get",
|
"get",
|
||||||
from_fn_async(get)
|
from_fn_async(get)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| {
|
.with_custom_display_fn(|handle, result| display_wifi_info(handle.params, result))
|
||||||
Ok(display_wifi_info(handle.params, result))
|
|
||||||
})
|
|
||||||
.with_about("List wifi info")
|
.with_about("List wifi info")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
@@ -134,7 +132,7 @@ pub fn available<C: Context>() -> ParentHandler<C> {
|
|||||||
"get",
|
"get",
|
||||||
from_fn_async(get_available)
|
from_fn_async(get_available)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| Ok(display_wifi_list(handle.params, result)))
|
.with_custom_display_fn(|handle, result| display_wifi_list(handle.params, result))
|
||||||
.with_about("List available wifi networks")
|
.with_about("List available wifi networks")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
@@ -363,7 +361,7 @@ pub struct WifiListOut {
|
|||||||
security: Vec<String>,
|
security: Vec<String>,
|
||||||
}
|
}
|
||||||
pub type WifiList = HashMap<Ssid, WifiListInfoLow>;
|
pub type WifiList = HashMap<Ssid, WifiListInfoLow>;
|
||||||
fn display_wifi_info(params: WithIoFormat<Empty>, info: WifiListInfo) {
|
fn display_wifi_info(params: WithIoFormat<Empty>, info: WifiListInfo) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -424,10 +422,11 @@ fn display_wifi_info(params: WithIoFormat<Empty>, info: WifiListInfo) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
table_global.print_tty(false).unwrap();
|
table_global.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_wifi_list(params: WithIoFormat<Empty>, info: Vec<WifiListOut>) {
|
fn display_wifi_list(params: WithIoFormat<Empty>, info: Vec<WifiListOut>) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -448,7 +447,8 @@ fn display_wifi_list(params: WithIoFormat<Empty>, info: Vec<WifiListOut>) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
table_global.print_tty(false).unwrap();
|
table_global.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[command(display(display_wifi_info))]
|
// #[command(display(display_wifi_info))]
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ lazy_static::lazy_static! {
|
|||||||
static ref BYTES: ProgressStyle = ProgressStyle::with_template("{spinner} {wide_msg} [{bytes}/?] [{binary_bytes_per_sec} {elapsed}]").unwrap();
|
static ref BYTES: ProgressStyle = ProgressStyle::with_template("{spinner} {wide_msg} [{bytes}/?] [{binary_bytes_per_sec} {elapsed}]").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, TS)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum ProgressUnits {
|
||||||
|
Bytes,
|
||||||
|
Steps,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, TS)]
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, TS)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Progress {
|
pub enum Progress {
|
||||||
@@ -34,13 +41,14 @@ pub enum Progress {
|
|||||||
done: u64,
|
done: u64,
|
||||||
#[ts(type = "number | null")]
|
#[ts(type = "number | null")]
|
||||||
total: Option<u64>,
|
total: Option<u64>,
|
||||||
|
units: Option<ProgressUnits>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
impl Progress {
|
impl Progress {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Progress::NotStarted(())
|
Progress::NotStarted(())
|
||||||
}
|
}
|
||||||
pub fn update_bar(self, bar: &ProgressBar, bytes: bool) {
|
pub fn update_bar(self, bar: &ProgressBar) {
|
||||||
match self {
|
match self {
|
||||||
Self::NotStarted(()) => {
|
Self::NotStarted(()) => {
|
||||||
bar.set_style(SPINNER.clone());
|
bar.set_style(SPINNER.clone());
|
||||||
@@ -52,8 +60,12 @@ impl Progress {
|
|||||||
Self::Complete(true) => {
|
Self::Complete(true) => {
|
||||||
bar.finish();
|
bar.finish();
|
||||||
}
|
}
|
||||||
Self::Progress { done, total: None } => {
|
Self::Progress {
|
||||||
if bytes {
|
done,
|
||||||
|
total: None,
|
||||||
|
units,
|
||||||
|
} => {
|
||||||
|
if units == Some(ProgressUnits::Bytes) {
|
||||||
bar.set_style(BYTES.clone());
|
bar.set_style(BYTES.clone());
|
||||||
} else {
|
} else {
|
||||||
bar.set_style(STEPS.clone());
|
bar.set_style(STEPS.clone());
|
||||||
@@ -64,8 +76,9 @@ impl Progress {
|
|||||||
Self::Progress {
|
Self::Progress {
|
||||||
done,
|
done,
|
||||||
total: Some(total),
|
total: Some(total),
|
||||||
|
units,
|
||||||
} => {
|
} => {
|
||||||
if bytes {
|
if units == Some(ProgressUnits::Bytes) {
|
||||||
bar.set_style(PERCENTAGE_BYTES.clone());
|
bar.set_style(PERCENTAGE_BYTES.clone());
|
||||||
} else {
|
} else {
|
||||||
bar.set_style(PERCENTAGE.clone());
|
bar.set_style(PERCENTAGE.clone());
|
||||||
@@ -84,14 +97,22 @@ impl Progress {
|
|||||||
}
|
}
|
||||||
pub fn set_done(&mut self, done: u64) {
|
pub fn set_done(&mut self, done: u64) {
|
||||||
*self = match *self {
|
*self = match *self {
|
||||||
Self::Complete(false) | Self::NotStarted(()) => Self::Progress { done, total: None },
|
Self::Complete(false) | Self::NotStarted(()) => Self::Progress {
|
||||||
Self::Progress { mut done, total } => {
|
done,
|
||||||
|
total: None,
|
||||||
|
units: None,
|
||||||
|
},
|
||||||
|
Self::Progress {
|
||||||
|
mut done,
|
||||||
|
total,
|
||||||
|
units,
|
||||||
|
} => {
|
||||||
if let Some(total) = total {
|
if let Some(total) = total {
|
||||||
if done > total {
|
if done > total {
|
||||||
done = total;
|
done = total;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Progress { done, total }
|
Self::Progress { done, total, units }
|
||||||
}
|
}
|
||||||
Self::Complete(true) => Self::Complete(true),
|
Self::Complete(true) => Self::Complete(true),
|
||||||
};
|
};
|
||||||
@@ -101,10 +122,12 @@ impl Progress {
|
|||||||
Self::Complete(false) | Self::NotStarted(()) => Self::Progress {
|
Self::Complete(false) | Self::NotStarted(()) => Self::Progress {
|
||||||
done: 0,
|
done: 0,
|
||||||
total: Some(total),
|
total: Some(total),
|
||||||
|
units: None,
|
||||||
},
|
},
|
||||||
Self::Progress { done, .. } => Self::Progress {
|
Self::Progress { done, units, .. } => Self::Progress {
|
||||||
done,
|
done,
|
||||||
total: Some(total),
|
total: Some(total),
|
||||||
|
units,
|
||||||
},
|
},
|
||||||
Self::Complete(true) => Self::Complete(true),
|
Self::Complete(true) => Self::Complete(true),
|
||||||
}
|
}
|
||||||
@@ -113,17 +136,30 @@ impl Progress {
|
|||||||
if let Self::Progress {
|
if let Self::Progress {
|
||||||
done,
|
done,
|
||||||
total: Some(old),
|
total: Some(old),
|
||||||
|
units,
|
||||||
} = *self
|
} = *self
|
||||||
{
|
{
|
||||||
*self = Self::Progress {
|
*self = Self::Progress {
|
||||||
done,
|
done,
|
||||||
total: Some(old + total),
|
total: Some(old + total),
|
||||||
|
units,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
self.set_total(total)
|
self.set_total(total)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn complete(&mut self) {
|
pub fn set_units(&mut self, units: Option<ProgressUnits>) {
|
||||||
|
*self = match *self {
|
||||||
|
Self::Complete(false) | Self::NotStarted(()) => Self::Progress {
|
||||||
|
done: 0,
|
||||||
|
total: None,
|
||||||
|
units,
|
||||||
|
},
|
||||||
|
Self::Progress { done, total, .. } => Self::Progress { done, total, units },
|
||||||
|
Self::Complete(true) => Self::Complete(true),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn set_complete(&mut self) {
|
||||||
*self = Self::Complete(true);
|
*self = Self::Complete(true);
|
||||||
}
|
}
|
||||||
pub fn is_complete(&self) -> bool {
|
pub fn is_complete(&self) -> bool {
|
||||||
@@ -137,15 +173,16 @@ impl std::ops::Add<u64> for Progress {
|
|||||||
Self::Complete(false) | Self::NotStarted(()) => Self::Progress {
|
Self::Complete(false) | Self::NotStarted(()) => Self::Progress {
|
||||||
done: rhs,
|
done: rhs,
|
||||||
total: None,
|
total: None,
|
||||||
|
units: None,
|
||||||
},
|
},
|
||||||
Self::Progress { done, total } => {
|
Self::Progress { done, total, units } => {
|
||||||
let mut done = done + rhs;
|
let mut done = done + rhs;
|
||||||
if let Some(total) = total {
|
if let Some(total) = total {
|
||||||
if done > total {
|
if done > total {
|
||||||
done = total;
|
done = total;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Progress { done, total }
|
Self::Progress { done, total, units }
|
||||||
}
|
}
|
||||||
Self::Complete(true) => Self::Complete(true),
|
Self::Complete(true) => Self::Complete(true),
|
||||||
}
|
}
|
||||||
@@ -337,7 +374,7 @@ impl FullProgressTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn complete(&self) {
|
pub fn complete(&self) {
|
||||||
self.overall.send_modify(|o| o.complete());
|
self.overall.send_modify(|o| o.set_complete());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,6 +392,7 @@ impl PhaseProgressTrackerHandle {
|
|||||||
Progress::Progress {
|
Progress::Progress {
|
||||||
done,
|
done,
|
||||||
total: Some(total),
|
total: Some(total),
|
||||||
|
..
|
||||||
} => ((done as f64 / total as f64) * overall_contribution as f64) as u64,
|
} => ((done as f64 / total as f64) * overall_contribution as f64) as u64,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
@@ -380,8 +418,11 @@ impl PhaseProgressTrackerHandle {
|
|||||||
self.progress.send_modify(|p| p.add_total(total));
|
self.progress.send_modify(|p| p.add_total(total));
|
||||||
self.update_overall();
|
self.update_overall();
|
||||||
}
|
}
|
||||||
|
pub fn set_units(&mut self, units: Option<ProgressUnits>) {
|
||||||
|
self.progress.send_modify(|p| p.set_units(units));
|
||||||
|
}
|
||||||
pub fn complete(&mut self) {
|
pub fn complete(&mut self) {
|
||||||
self.progress.send_modify(|p| p.complete());
|
self.progress.send_modify(|p| p.set_complete());
|
||||||
self.update_overall();
|
self.update_overall();
|
||||||
}
|
}
|
||||||
pub fn writer<W>(self, writer: W) -> ProgressTrackerWriter<W> {
|
pub fn writer<W>(self, writer: W) -> ProgressTrackerWriter<W> {
|
||||||
@@ -501,7 +542,7 @@ impl PhasedProgressBar {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
progress.overall.update_bar(&self.overall, false);
|
progress.overall.update_bar(&self.overall);
|
||||||
for (name, bar) in self.phases.iter() {
|
for (name, bar) in self.phases.iter() {
|
||||||
if let Some(progress) = progress.phases.iter().find_map(|p| {
|
if let Some(progress) = progress.phases.iter().find_map(|p| {
|
||||||
if &p.name == name {
|
if &p.name == name {
|
||||||
@@ -510,7 +551,7 @@ impl PhasedProgressBar {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
progress.update_bar(bar, true);
|
progress.update_bar(bar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ pub fn admin_api<C: Context>() -> ParentHandler<C> {
|
|||||||
from_fn_async(list_admins)
|
from_fn_async(list_admins)
|
||||||
.with_metadata("admin", Value::Bool(true))
|
.with_metadata("admin", Value::Bool(true))
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result)))
|
.with_custom_display_fn(|handle, result| display_signers(handle.params, result))
|
||||||
.with_about("List admin signers")
|
.with_about("List admin signers")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
@@ -60,7 +60,7 @@ fn signers_api<C: Context>() -> ParentHandler<C> {
|
|||||||
from_fn_async(list_signers)
|
from_fn_async(list_signers)
|
||||||
.with_metadata("admin", Value::Bool(true))
|
.with_metadata("admin", Value::Bool(true))
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result)))
|
.with_custom_display_fn(|handle, result| display_signers(handle.params, result))
|
||||||
.with_about("List signers")
|
.with_about("List signers")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
@@ -133,7 +133,10 @@ pub async fn list_signers(ctx: RegistryContext) -> Result<BTreeMap<Guid, SignerI
|
|||||||
ctx.db.peek().await.into_index().into_signers().de()
|
ctx.db.peek().await.into_index().into_signers().de()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_signers<T>(params: WithIoFormat<T>, signers: BTreeMap<Guid, SignerInfo>) {
|
pub fn display_signers<T>(
|
||||||
|
params: WithIoFormat<T>,
|
||||||
|
signers: BTreeMap<Guid, SignerInfo>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -155,7 +158,8 @@ pub fn display_signers<T>(params: WithIoFormat<T>, signers: BTreeMap<Guid, Signe
|
|||||||
&info.keys.into_iter().join("\n"),
|
&info.keys.into_iter().join("\n"),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_signer(ctx: RegistryContext, signer: SignerInfo) -> Result<Guid, Error> {
|
pub async fn add_signer(ctx: RegistryContext, signer: SignerInfo) -> Result<Guid, Error> {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use url::Url;
|
|||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::FullProgressTracker;
|
use crate::progress::{FullProgressTracker, ProgressUnits};
|
||||||
use crate::registry::asset::RegistryAsset;
|
use crate::registry::asset::RegistryAsset;
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::os::index::OsVersionInfo;
|
use crate::registry::os::index::OsVersionInfo;
|
||||||
@@ -246,6 +246,7 @@ pub async fn cli_add_asset(
|
|||||||
if let Some(size) = src.size().await {
|
if let Some(size) = src.size().await {
|
||||||
verify_phase.set_total(size);
|
verify_phase.set_total(size);
|
||||||
}
|
}
|
||||||
|
verify_phase.set_units(Some(ProgressUnits::Bytes));
|
||||||
let mut writer = verify_phase.writer(VerifyingWriter::new(
|
let mut writer = verify_phase.writer(VerifyingWriter::new(
|
||||||
tokio::io::sink(),
|
tokio::io::sink(),
|
||||||
Some((blake3::Hash::from_bytes(*commitment.hash), commitment.size)),
|
Some((blake3::Hash::from_bytes(*commitment.hash), commitment.size)),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use ts_rs::TS;
|
|||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::FullProgressTracker;
|
use crate::progress::{FullProgressTracker, ProgressUnits};
|
||||||
use crate::registry::asset::RegistryAsset;
|
use crate::registry::asset::RegistryAsset;
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::os::index::OsVersionInfo;
|
use crate::registry::os::index::OsVersionInfo;
|
||||||
@@ -167,6 +167,7 @@ async fn cli_get_os_asset(
|
|||||||
let mut download_phase =
|
let mut download_phase =
|
||||||
progress.add_phase(InternedString::intern("Downloading File"), Some(100));
|
progress.add_phase(InternedString::intern("Downloading File"), Some(100));
|
||||||
download_phase.set_total(res.commitment.size);
|
download_phase.set_total(res.commitment.size);
|
||||||
|
download_phase.set_units(Some(ProgressUnits::Bytes));
|
||||||
let reverify_phase = if reverify {
|
let reverify_phase = if reverify {
|
||||||
Some(progress.add_phase(InternedString::intern("Reverifying File"), Some(10)))
|
Some(progress.add_phase(InternedString::intern("Reverifying File"), Some(10)))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pub fn version_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.with_metadata("get_device_info", Value::Bool(true))
|
.with_metadata("get_device_info", Value::Bool(true))
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| {
|
.with_custom_display_fn(|handle, result| {
|
||||||
Ok(display_version_info(handle.params, result))
|
display_version_info(handle.params, result)
|
||||||
})
|
})
|
||||||
.with_about("Get OS versions and related version info")
|
.with_about("Get OS versions and related version info")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
@@ -197,7 +197,10 @@ pub async fn get_version(
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_version_info<T>(params: WithIoFormat<T>, info: BTreeMap<Version, OsVersionInfo>) {
|
pub fn display_version_info<T>(
|
||||||
|
params: WithIoFormat<T>,
|
||||||
|
info: BTreeMap<Version, OsVersionInfo>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -223,5 +226,6 @@ pub fn display_version_info<T>(params: WithIoFormat<T>, info: BTreeMap<Version,
|
|||||||
&info.squashfs.keys().into_iter().join(", "),
|
&info.squashfs.keys().into_iter().join(", "),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ pub fn signer_api<C: Context>() -> ParentHandler<C> {
|
|||||||
"list",
|
"list",
|
||||||
from_fn_async(list_version_signers)
|
from_fn_async(list_version_signers)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result)))
|
.with_custom_display_fn(|handle, result| display_signers(handle.params, result))
|
||||||
.with_about("List version signers and related signer info")
|
.with_about("List version signers and related signer info")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use url::Url;
|
|||||||
|
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{FullProgressTracker, ProgressTrackerWriter};
|
use crate::progress::{FullProgressTracker, ProgressTrackerWriter, ProgressUnits};
|
||||||
use crate::registry::context::RegistryContext;
|
use crate::registry::context::RegistryContext;
|
||||||
use crate::registry::package::index::PackageVersionInfo;
|
use crate::registry::package::index::PackageVersionInfo;
|
||||||
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
use crate::registry::signer::commitment::merkle_archive::MerkleArchiveCommitment;
|
||||||
@@ -135,6 +135,7 @@ pub async fn cli_add_package(
|
|||||||
if let Some(len) = len {
|
if let Some(len) = len {
|
||||||
verify_phase.set_total(len);
|
verify_phase.set_total(len);
|
||||||
}
|
}
|
||||||
|
verify_phase.set_units(Some(ProgressUnits::Bytes));
|
||||||
let mut verify_writer = ProgressTrackerWriter::new(tokio::io::sink(), verify_phase);
|
let mut verify_writer = ProgressTrackerWriter::new(tokio::io::sink(), verify_phase);
|
||||||
src.serialize(&mut TrackingIO::new(0, &mut verify_writer), true)
|
src.serialize(&mut TrackingIO::new(0, &mut verify_writer), true)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ pub fn category_api<C: Context>() -> ParentHandler<C> {
|
|||||||
from_fn_async(list_categories)
|
from_fn_async(list_categories)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|params, categories| {
|
.with_custom_display_fn(|params, categories| {
|
||||||
Ok(display_categories(params.params, categories))
|
display_categories(params.params, categories)
|
||||||
})
|
})
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
@@ -182,7 +182,7 @@ pub async fn list_categories(
|
|||||||
pub fn display_categories<T>(
|
pub fn display_categories<T>(
|
||||||
params: WithIoFormat<T>,
|
params: WithIoFormat<T>,
|
||||||
categories: BTreeMap<InternedString, Category>,
|
categories: BTreeMap<InternedString, Category>,
|
||||||
) {
|
) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -197,5 +197,6 @@ pub fn display_categories<T>(
|
|||||||
for (id, info) in categories {
|
for (id, info) in categories {
|
||||||
table.add_row(row![&*id, &info.name]);
|
table.add_row(row![&*id, &info.name]);
|
||||||
}
|
}
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ pub fn signer_api<C: Context>() -> ParentHandler<C> {
|
|||||||
"list",
|
"list",
|
||||||
from_fn_async(list_package_signers)
|
from_fn_async(list_package_signers)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result)))
|
.with_custom_display_fn(|handle, result| display_signers(handle.params, result))
|
||||||
.with_about("List package signers and related signer info")
|
.with_about("List package signers and related signer info")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::str::FromStr;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use exver::{ExtendedVersion, VersionRange};
|
use exver::{ExtendedVersion, VersionRange};
|
||||||
use models::{Id, ImageId, VolumeId};
|
use models::{ImageId, VolumeId};
|
||||||
use tokio::io::{AsyncRead, AsyncSeek, AsyncWriteExt};
|
use tokio::io::{AsyncRead, AsyncSeek, AsyncWriteExt};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
@@ -259,6 +259,7 @@ impl TryFrom<ManifestV1> for Manifest {
|
|||||||
},
|
},
|
||||||
git_hash: value.git_hash,
|
git_hash: value.git_hash,
|
||||||
os_version: value.eos_version,
|
os_version: value.eos_version,
|
||||||
|
sdk_version: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ pub struct Manifest {
|
|||||||
#[serde(default = "current_version")]
|
#[serde(default = "current_version")]
|
||||||
#[ts(type = "string")]
|
#[ts(type = "string")]
|
||||||
pub os_version: Version,
|
pub os_version: Version,
|
||||||
|
#[ts(type = "string | null")]
|
||||||
|
pub sdk_version: Option<Version>,
|
||||||
}
|
}
|
||||||
impl Manifest {
|
impl Manifest {
|
||||||
pub fn validate_for<'a, T: Clone>(
|
pub fn validate_for<'a, T: Clone>(
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
|
|||||||
"run",
|
"run",
|
||||||
from_fn_async(run_action)
|
from_fn_async(run_action)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|args, res| Ok(display_action_result(args.params, res)))
|
.with_custom_display_fn(|args, res| display_action_result(args.params, res))
|
||||||
.with_call_remote::<ContainerCliContext>(),
|
.with_call_remote::<ContainerCliContext>(),
|
||||||
)
|
)
|
||||||
.subcommand("create-task", from_fn_async(create_task).no_cli())
|
.subcommand("create-task", from_fn_async(create_task).no_cli())
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
|
|||||||
use std::ffi::{c_int, OsStr, OsString};
|
use std::ffi::{c_int, OsStr, OsString};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{IsTerminal, Read};
|
use std::io::{IsTerminal, Read};
|
||||||
use std::os::unix::process::CommandExt;
|
use std::os::unix::process::{CommandExt, ExitStatusExt};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command as StdCommand, Stdio};
|
use std::process::{Command as StdCommand, Stdio};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -330,7 +330,7 @@ pub fn launch(
|
|||||||
if let Some(code) = exit.code() {
|
if let Some(code) = exit.code() {
|
||||||
drop(raw);
|
drop(raw);
|
||||||
std::process::exit(code);
|
std::process::exit(code);
|
||||||
} else if exit.success() {
|
} else if exit.success() || exit.signal() == Some(15) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new(
|
Err(Error::new(
|
||||||
@@ -380,7 +380,7 @@ pub fn launch(
|
|||||||
nix::mount::umount(&chroot.join("proc"))
|
nix::mount::umount(&chroot.join("proc"))
|
||||||
.with_ctx(|_| (ErrorKind::Filesystem, "umount procfs"))?;
|
.with_ctx(|_| (ErrorKind::Filesystem, "umount procfs"))?;
|
||||||
std::process::exit(code);
|
std::process::exit(code);
|
||||||
} else if exit.success() {
|
} else if exit.success() || exit.signal() == Some(15) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new(
|
Err(Error::new(
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ use crate::s9pk::S9pk;
|
|||||||
use crate::service::action::update_tasks;
|
use crate::service::action::update_tasks;
|
||||||
use crate::service::rpc::{ExitParams, InitKind};
|
use crate::service::rpc::{ExitParams, InitKind};
|
||||||
use crate::service::service_map::InstallProgressHandles;
|
use crate::service::service_map::InstallProgressHandles;
|
||||||
|
use crate::service::uninstall::cleanup;
|
||||||
use crate::util::actor::concurrent::ConcurrentActor;
|
use crate::util::actor::concurrent::ConcurrentActor;
|
||||||
use crate::util::io::{create_file, AsyncReadStream, TermSize};
|
use crate::util::io::{create_file, AsyncReadStream, TermSize};
|
||||||
use crate::util::net::WebSocketExt;
|
use crate::util::net::WebSocketExt;
|
||||||
@@ -111,7 +112,6 @@ impl std::fmt::Display for MiB {
|
|||||||
#[derive(Clone, Debug, Serialize, Deserialize, Default, TS)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Default, TS)]
|
||||||
pub struct ServiceStats {
|
pub struct ServiceStats {
|
||||||
pub container_id: Arc<ContainerId>,
|
pub container_id: Arc<ContainerId>,
|
||||||
pub package_id: PackageId,
|
|
||||||
pub memory_usage: MiB,
|
pub memory_usage: MiB,
|
||||||
pub memory_limit: MiB,
|
pub memory_limit: MiB,
|
||||||
}
|
}
|
||||||
@@ -307,7 +307,7 @@ impl Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: delete s9pk?
|
cleanup(ctx, id, false).await.log_err();
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|v| v.as_public_mut().as_package_data_mut().remove(id))
|
.mutate(|v| v.as_public_mut().as_package_data_mut().remove(id))
|
||||||
.await
|
.await
|
||||||
@@ -615,7 +615,6 @@ impl Service {
|
|||||||
.fold((0, 0), |acc, (total, used)| (acc.0 + total, acc.1 + used));
|
.fold((0, 0), |acc, (total, used)| (acc.0 + total, acc.1 + used));
|
||||||
Ok(ServiceStats {
|
Ok(ServiceStats {
|
||||||
container_id: lxc_container.guid.clone(),
|
container_id: lxc_container.guid.clone(),
|
||||||
package_id: self.seed.id.clone(),
|
|
||||||
memory_limit: MiB::from_MiB(total),
|
memory_limit: MiB::from_MiB(total),
|
||||||
memory_usage: MiB::from_MiB(used),
|
memory_usage: MiB::from_MiB(used),
|
||||||
})
|
})
|
||||||
@@ -735,6 +734,7 @@ pub struct AttachParams {
|
|||||||
pub command: Vec<OsString>,
|
pub command: Vec<OsString>,
|
||||||
pub tty: bool,
|
pub tty: bool,
|
||||||
pub stderr_tty: bool,
|
pub stderr_tty: bool,
|
||||||
|
pub pty_size: Option<TermSize>,
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
#[serde(rename = "__auth_session")]
|
#[serde(rename = "__auth_session")]
|
||||||
session: Option<InternedString>,
|
session: Option<InternedString>,
|
||||||
@@ -752,6 +752,7 @@ pub async fn attach(
|
|||||||
command,
|
command,
|
||||||
tty,
|
tty,
|
||||||
stderr_tty,
|
stderr_tty,
|
||||||
|
pty_size,
|
||||||
session,
|
session,
|
||||||
subcontainer,
|
subcontainer,
|
||||||
image_id,
|
image_id,
|
||||||
@@ -862,6 +863,7 @@ pub async fn attach(
|
|||||||
command: Vec<OsString>,
|
command: Vec<OsString>,
|
||||||
tty: bool,
|
tty: bool,
|
||||||
stderr_tty: bool,
|
stderr_tty: bool,
|
||||||
|
pty_size: Option<TermSize>,
|
||||||
image_id: ImageId,
|
image_id: ImageId,
|
||||||
workdir: Option<String>,
|
workdir: Option<String>,
|
||||||
root_command: &RootCommand,
|
root_command: &RootCommand,
|
||||||
@@ -898,6 +900,10 @@ pub async fn attach(
|
|||||||
cmd.arg("--force-stderr-tty");
|
cmd.arg("--force-stderr-tty");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(pty_size) = pty_size {
|
||||||
|
cmd.arg(format!("--pty-size={pty_size}"));
|
||||||
|
}
|
||||||
|
|
||||||
cmd.arg(&root_path).arg("--");
|
cmd.arg(&root_path).arg("--");
|
||||||
|
|
||||||
if command.is_empty() {
|
if command.is_empty() {
|
||||||
@@ -1040,6 +1046,7 @@ pub async fn attach(
|
|||||||
command,
|
command,
|
||||||
tty,
|
tty,
|
||||||
stderr_tty,
|
stderr_tty,
|
||||||
|
pty_size,
|
||||||
image_id,
|
image_id,
|
||||||
workdir,
|
workdir,
|
||||||
&root_command,
|
&root_command,
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ use crate::disk::mount::guard::GenericMountGuard;
|
|||||||
use crate::install::PKG_ARCHIVE_DIR;
|
use crate::install::PKG_ARCHIVE_DIR;
|
||||||
use crate::notifications::{notify, NotificationLevel};
|
use crate::notifications::{notify, NotificationLevel};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle, ProgressTrackerWriter};
|
use crate::progress::{
|
||||||
|
FullProgressTracker, PhaseProgressTrackerHandle, ProgressTrackerWriter, ProgressUnits,
|
||||||
|
};
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::s9pk::merkle_archive::source::FileSource;
|
use crate::s9pk::merkle_archive::source::FileSource;
|
||||||
@@ -72,6 +74,7 @@ impl ServiceMap {
|
|||||||
progress.start();
|
progress.start();
|
||||||
let ids = ctx.db.peek().await.as_public().as_package_data().keys()?;
|
let ids = ctx.db.peek().await.as_public().as_package_data().keys()?;
|
||||||
progress.set_total(ids.len() as u64);
|
progress.set_total(ids.len() as u64);
|
||||||
|
progress.set_units(Some(ProgressUnits::Steps));
|
||||||
let mut jobs = FuturesUnordered::new();
|
let mut jobs = FuturesUnordered::new();
|
||||||
for id in &ids {
|
for id in &ids {
|
||||||
jobs.push(self.load(ctx, id, LoadDisposition::Retry));
|
jobs.push(self.load(ctx, id, LoadDisposition::Retry));
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ use crate::disk::REPAIR_DISK_PATH;
|
|||||||
use crate::init::{init, InitPhases, InitResult};
|
use crate::init::{init, InitPhases, InitResult};
|
||||||
use crate::net::ssl::root_ca_start_time;
|
use crate::net::ssl::root_ca_start_time;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{FullProgress, PhaseProgressTrackerHandle};
|
use crate::progress::{FullProgress, PhaseProgressTrackerHandle, ProgressUnits};
|
||||||
use crate::rpc_continuations::Guid;
|
use crate::rpc_continuations::Guid;
|
||||||
use crate::system::sync_kiosk;
|
use crate::system::sync_kiosk;
|
||||||
use crate::util::crypto::EncryptedWire;
|
use crate::util::crypto::EncryptedWire;
|
||||||
@@ -547,6 +547,7 @@ async fn migrate(
|
|||||||
let mut restore_phase = restore_phase.or_not_found("restore progress")?;
|
let mut restore_phase = restore_phase.or_not_found("restore progress")?;
|
||||||
|
|
||||||
restore_phase.start();
|
restore_phase.start();
|
||||||
|
restore_phase.set_units(Some(ProgressUnits::Bytes));
|
||||||
let _ = crate::disk::main::import(
|
let _ = crate::disk::main::import(
|
||||||
&old_guid,
|
&old_guid,
|
||||||
"/media/startos/migrate",
|
"/media/startos/migrate",
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ pub fn ssh<C: Context>() -> ParentHandler<C> {
|
|||||||
from_fn_async(list)
|
from_fn_async(list)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| {
|
.with_custom_display_fn(|handle, result| {
|
||||||
Ok(display_all_ssh_keys(handle.params, result))
|
display_all_ssh_keys(handle.params, result)
|
||||||
})
|
})
|
||||||
.with_about("List ssh keys")
|
.with_about("List ssh keys")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
@@ -177,7 +177,10 @@ pub async fn remove(
|
|||||||
sync_pubkeys(&keys, SSH_DIR).await
|
sync_pubkeys(&keys, SSH_DIR).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_all_ssh_keys(params: WithIoFormat<Empty>, result: Vec<SshKeyResponse>) {
|
fn display_all_ssh_keys(
|
||||||
|
params: WithIoFormat<Empty>,
|
||||||
|
result: Vec<SshKeyResponse>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -200,7 +203,9 @@ fn display_all_ssh_keys(params: WithIoFormat<Empty>, result: Vec<SshKeyResponse>
|
|||||||
];
|
];
|
||||||
table.add_row(row);
|
table.add_row(row);
|
||||||
}
|
}
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ pub fn experimental<C: Context>() -> ParentHandler<C> {
|
|||||||
from_fn_async(governor)
|
from_fn_async(governor)
|
||||||
.with_display_serializable()
|
.with_display_serializable()
|
||||||
.with_custom_display_fn(|handle, result| {
|
.with_custom_display_fn(|handle, result| {
|
||||||
Ok(display_governor_info(handle.params, result))
|
display_governor_info(handle.params, result)
|
||||||
})
|
})
|
||||||
.with_about("Show current and available CPU governors")
|
.with_about("Show current and available CPU governors")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
@@ -125,7 +125,10 @@ pub struct GovernorInfo {
|
|||||||
available: BTreeSet<Governor>,
|
available: BTreeSet<Governor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_governor_info(params: WithIoFormat<GovernorParams>, result: GovernorInfo) {
|
fn display_governor_info(
|
||||||
|
params: WithIoFormat<GovernorParams>,
|
||||||
|
result: GovernorInfo,
|
||||||
|
) -> Result<(), Error> {
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
|
|
||||||
if let Some(format) = params.format {
|
if let Some(format) = params.format {
|
||||||
@@ -141,7 +144,8 @@ fn display_governor_info(params: WithIoFormat<GovernorParams>, result: GovernorI
|
|||||||
table.add_row(row![entry]);
|
table.add_row(row![entry]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Parser, TS)]
|
#[derive(Deserialize, Serialize, Parser, TS)]
|
||||||
@@ -191,7 +195,7 @@ pub struct TimeInfo {
|
|||||||
uptime: u64,
|
uptime: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_time(params: WithIoFormat<Empty>, arg: TimeInfo) {
|
pub fn display_time(params: WithIoFormat<Empty>, arg: TimeInfo) -> Result<(), Error> {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use prettytable::*;
|
use prettytable::*;
|
||||||
@@ -230,7 +234,8 @@ pub fn display_time(params: WithIoFormat<Empty>, arg: TimeInfo) {
|
|||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
table.add_row(row![bc -> "NOW", &arg.now]);
|
table.add_row(row![bc -> "NOW", &arg.now]);
|
||||||
table.add_row(row![bc -> "UPTIME", &uptime_string]);
|
table.add_row(row![bc -> "UPTIME", &uptime_string]);
|
||||||
table.print_tty(false).unwrap();
|
table.print_tty(false)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn time(ctx: RpcContext, _: Empty) -> Result<TimeInfo, Error> {
|
pub async fn time(ctx: RpcContext, _: Empty) -> Result<TimeInfo, Error> {
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ use crate::disk::mount::filesystem::MountType;
|
|||||||
use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard};
|
use crate::disk::mount::guard::{GenericMountGuard, MountGuard, TmpMountGuard};
|
||||||
use crate::notifications::{notify, NotificationLevel};
|
use crate::notifications::{notify, NotificationLevel};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::{FullProgressTracker, PhaseProgressTrackerHandle, PhasedProgressBar};
|
use crate::progress::{
|
||||||
|
FullProgressTracker, PhaseProgressTrackerHandle, PhasedProgressBar, ProgressUnits,
|
||||||
|
};
|
||||||
use crate::registry::asset::RegistryAsset;
|
use crate::registry::asset::RegistryAsset;
|
||||||
use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
||||||
use crate::registry::os::index::OsVersionInfo;
|
use crate::registry::os::index::OsVersionInfo;
|
||||||
@@ -195,9 +197,9 @@ pub async fn cli_update_system(
|
|||||||
}
|
}
|
||||||
if let Some(mut prev) = prev {
|
if let Some(mut prev) = prev {
|
||||||
for phase in &mut prev.phases {
|
for phase in &mut prev.phases {
|
||||||
phase.progress.complete();
|
phase.progress.set_complete();
|
||||||
}
|
}
|
||||||
prev.overall.complete();
|
prev.overall.set_complete();
|
||||||
progress.update(&prev);
|
progress.update(&prev);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -265,6 +267,7 @@ async fn maybe_do_update(
|
|||||||
let prune_phase = progress.add_phase("Pruning Old OS Images".into(), Some(2));
|
let prune_phase = progress.add_phase("Pruning Old OS Images".into(), Some(2));
|
||||||
let mut download_phase = progress.add_phase("Downloading File".into(), Some(100));
|
let mut download_phase = progress.add_phase("Downloading File".into(), Some(100));
|
||||||
download_phase.set_total(asset.commitment.size);
|
download_phase.set_total(asset.commitment.size);
|
||||||
|
download_phase.set_units(Some(ProgressUnits::Bytes));
|
||||||
let reverify_phase = progress.add_phase("Reverifying File".into(), Some(10));
|
let reverify_phase = progress.add_phase("Reverifying File".into(), Some(10));
|
||||||
let sync_boot_phase = progress.add_phase("Syncing Boot Files".into(), Some(1));
|
let sync_boot_phase = progress.add_phase("Syncing Boot Files".into(), Some(1));
|
||||||
let finalize_phase = progress.add_phase("Finalizing Update".into(), Some(1));
|
let finalize_phase = progress.add_phase("Finalizing Update".into(), Some(1));
|
||||||
@@ -399,6 +402,9 @@ async fn do_update(
|
|||||||
.arg(asset.commitment.size.to_string())
|
.arg(asset.commitment.size.to_string())
|
||||||
.invoke(ErrorKind::Filesystem)
|
.invoke(ErrorKind::Filesystem)
|
||||||
.await?;
|
.await?;
|
||||||
|
Command::new("/usr/lib/startos/scripts/prune-boot")
|
||||||
|
.invoke(ErrorKind::Filesystem)
|
||||||
|
.await?;
|
||||||
prune_phase.complete();
|
prune_phase.complete();
|
||||||
|
|
||||||
download_phase.start();
|
download_phase.start();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use tokio::sync::watch;
|
|||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::progress::PhaseProgressTrackerHandle;
|
use crate::progress::{PhaseProgressTrackerHandle, ProgressUnits};
|
||||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||||
use crate::s9pk::merkle_archive::source::multi_cursor_file::{FileCursor, MultiCursorFile};
|
use crate::s9pk::merkle_archive::source::multi_cursor_file::{FileCursor, MultiCursorFile};
|
||||||
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
||||||
@@ -176,7 +176,10 @@ pub struct UploadingFile {
|
|||||||
progress: watch::Receiver<Progress>,
|
progress: watch::Receiver<Progress>,
|
||||||
}
|
}
|
||||||
impl UploadingFile {
|
impl UploadingFile {
|
||||||
pub async fn new(progress: PhaseProgressTrackerHandle) -> Result<(UploadHandle, Self), Error> {
|
pub async fn new(
|
||||||
|
mut progress: PhaseProgressTrackerHandle,
|
||||||
|
) -> Result<(UploadHandle, Self), Error> {
|
||||||
|
progress.set_units(Some(ProgressUnits::Bytes));
|
||||||
let progress = watch::channel(Progress {
|
let progress = watch::channel(Progress {
|
||||||
tracker: progress,
|
tracker: progress,
|
||||||
expected_size: None,
|
expected_size: None,
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ use tokio::io::{
|
|||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::sync::{Notify, OwnedMutexGuard};
|
use tokio::sync::{Notify, OwnedMutexGuard};
|
||||||
use tokio::time::{Instant, Sleep};
|
use tokio::time::{Instant, Sleep};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::util::sync::SyncMutex;
|
use crate::util::sync::SyncMutex;
|
||||||
use crate::{CAP_1_KiB, CAP_1_MiB};
|
|
||||||
|
|
||||||
pub trait AsyncReadSeek: AsyncRead + AsyncSeek {}
|
pub trait AsyncReadSeek: AsyncRead + AsyncSeek {}
|
||||||
impl<T: AsyncRead + AsyncSeek> AsyncReadSeek for T {}
|
impl<T: AsyncRead + AsyncSeek> AsyncReadSeek for T {}
|
||||||
@@ -1426,7 +1426,7 @@ impl<T: std::io::Read> std::io::Read for SharedIO<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||||
pub struct TermSize {
|
pub struct TermSize {
|
||||||
pub size: (u16, u16),
|
pub size: (u16, u16),
|
||||||
pub pixels: Option<(u16, u16)>,
|
pub pixels: Option<(u16, u16)>,
|
||||||
@@ -1464,6 +1464,15 @@ impl FromStr for TermSize {
|
|||||||
.ok_or_else(|| Error::new(eyre!("invalid pty size"), ErrorKind::ParseNumber))
|
.ok_or_else(|| Error::new(eyre!("invalid pty size"), ErrorKind::ParseNumber))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl std::fmt::Display for TermSize {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}:{}", self.size.0, self.size.1)?;
|
||||||
|
if let Some(pixels) = self.pixels {
|
||||||
|
write!(f, ":{}:{}", pixels.0, pixels.1)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
impl ValueParserFactory for TermSize {
|
impl ValueParserFactory for TermSize {
|
||||||
type Parser = FromStrParser<Self>;
|
type Parser = FromStrParser<Self>;
|
||||||
fn value_parser() -> Self::Parser {
|
fn value_parser() -> Self::Parser {
|
||||||
|
|||||||
@@ -398,13 +398,12 @@ impl IoFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_serializable<T: Serialize>(format: IoFormat, result: T) {
|
pub fn display_serializable<T: Serialize>(format: IoFormat, result: T) -> Result<(), Error> {
|
||||||
format
|
format.to_writer(std::io::stdout(), &result)?;
|
||||||
.to_writer(std::io::stdout(), &result)
|
|
||||||
.expect("Error serializing result to stdout");
|
|
||||||
if format == IoFormat::JsonPretty {
|
if format == IoFormat::JsonPretty {
|
||||||
println!()
|
println!()
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
@@ -523,13 +522,14 @@ impl<T: HandlerFor<C>, C: Context> HandlerFor<C> for DisplaySerializable<T> {
|
|||||||
impl<T: HandlerTypes, C: Context> PrintCliResult<C> for DisplaySerializable<T>
|
impl<T: HandlerTypes, C: Context> PrintCliResult<C> for DisplaySerializable<T>
|
||||||
where
|
where
|
||||||
T::Ok: Serialize,
|
T::Ok: Serialize,
|
||||||
|
Self::Err: From<Error>,
|
||||||
{
|
{
|
||||||
fn print(
|
fn print(
|
||||||
&self,
|
&self,
|
||||||
HandlerArgs { params, .. }: HandlerArgsFor<C, Self>,
|
HandlerArgs { params, .. }: HandlerArgsFor<C, Self>,
|
||||||
result: Self::Ok,
|
result: Self::Ok,
|
||||||
) -> Result<(), Self::Err> {
|
) -> Result<(), Self::Err> {
|
||||||
display_serializable(params.format.unwrap_or_default(), result);
|
display_serializable(params.format.unwrap_or_default(), result)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ mod v0_4_0_alpha_3;
|
|||||||
mod v0_4_0_alpha_4;
|
mod v0_4_0_alpha_4;
|
||||||
mod v0_4_0_alpha_5;
|
mod v0_4_0_alpha_5;
|
||||||
mod v0_4_0_alpha_6;
|
mod v0_4_0_alpha_6;
|
||||||
|
mod v0_4_0_alpha_7;
|
||||||
|
|
||||||
pub type Current = v0_4_0_alpha_6::Version; // VERSION_BUMP
|
pub type Current = v0_4_0_alpha_7::Version; // VERSION_BUMP
|
||||||
|
|
||||||
impl Current {
|
impl Current {
|
||||||
#[instrument(skip(self, db))]
|
#[instrument(skip(self, db))]
|
||||||
@@ -157,7 +158,8 @@ enum Version {
|
|||||||
V0_4_0_alpha_3(Wrapper<v0_4_0_alpha_3::Version>),
|
V0_4_0_alpha_3(Wrapper<v0_4_0_alpha_3::Version>),
|
||||||
V0_4_0_alpha_4(Wrapper<v0_4_0_alpha_4::Version>),
|
V0_4_0_alpha_4(Wrapper<v0_4_0_alpha_4::Version>),
|
||||||
V0_4_0_alpha_5(Wrapper<v0_4_0_alpha_5::Version>),
|
V0_4_0_alpha_5(Wrapper<v0_4_0_alpha_5::Version>),
|
||||||
V0_4_0_alpha_6(Wrapper<v0_4_0_alpha_6::Version>), // VERSION_BUMP
|
V0_4_0_alpha_6(Wrapper<v0_4_0_alpha_6::Version>),
|
||||||
|
V0_4_0_alpha_7(Wrapper<v0_4_0_alpha_7::Version>), // VERSION_BUMP
|
||||||
Other(exver::Version),
|
Other(exver::Version),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +208,8 @@ impl Version {
|
|||||||
Self::V0_4_0_alpha_3(v) => DynVersion(Box::new(v.0)),
|
Self::V0_4_0_alpha_3(v) => DynVersion(Box::new(v.0)),
|
||||||
Self::V0_4_0_alpha_4(v) => DynVersion(Box::new(v.0)),
|
Self::V0_4_0_alpha_4(v) => DynVersion(Box::new(v.0)),
|
||||||
Self::V0_4_0_alpha_5(v) => DynVersion(Box::new(v.0)),
|
Self::V0_4_0_alpha_5(v) => DynVersion(Box::new(v.0)),
|
||||||
Self::V0_4_0_alpha_6(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
Self::V0_4_0_alpha_6(v) => DynVersion(Box::new(v.0)),
|
||||||
|
Self::V0_4_0_alpha_7(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||||
Self::Other(v) => {
|
Self::Other(v) => {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("unknown version {v}"),
|
eyre!("unknown version {v}"),
|
||||||
@@ -247,7 +250,8 @@ impl Version {
|
|||||||
Version::V0_4_0_alpha_3(Wrapper(x)) => x.semver(),
|
Version::V0_4_0_alpha_3(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_4_0_alpha_4(Wrapper(x)) => x.semver(),
|
Version::V0_4_0_alpha_4(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_4_0_alpha_5(Wrapper(x)) => x.semver(),
|
Version::V0_4_0_alpha_5(Wrapper(x)) => x.semver(),
|
||||||
Version::V0_4_0_alpha_6(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
Version::V0_4_0_alpha_6(Wrapper(x)) => x.semver(),
|
||||||
|
Version::V0_4_0_alpha_7(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||||
Version::Other(x) => x.clone(),
|
Version::Other(x) => x.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
37
core/startos/src/version/v0_4_0_alpha_7.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use exver::{PreReleaseSegment, VersionRange};
|
||||||
|
|
||||||
|
use super::v0_3_5::V0_3_0_COMPAT;
|
||||||
|
use super::{v0_4_0_alpha_6, VersionT};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref V0_4_0_alpha_7: exver::Version = exver::Version::new(
|
||||||
|
[0, 4, 0],
|
||||||
|
[PreReleaseSegment::String("alpha".into()), 7.into()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Version;
|
||||||
|
|
||||||
|
impl VersionT for Version {
|
||||||
|
type Previous = v0_4_0_alpha_6::Version;
|
||||||
|
type PreUpRes = ();
|
||||||
|
|
||||||
|
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn semver(self) -> exver::Version {
|
||||||
|
V0_4_0_alpha_7.clone()
|
||||||
|
}
|
||||||
|
fn compat(self) -> &'static VersionRange {
|
||||||
|
&V0_3_0_COMPAT
|
||||||
|
}
|
||||||
|
#[instrument]
|
||||||
|
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
25
debian/postinst
vendored
@@ -25,12 +25,33 @@ if [ -f /etc/default/grub ]; then
|
|||||||
sed -i '/\(^\|#\)GRUB_TERMINAL=/c\GRUB_TERMINAL="serial"\nGRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"' /etc/default/grub
|
sed -i '/\(^\|#\)GRUB_TERMINAL=/c\GRUB_TERMINAL="serial"\nGRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"' /etc/default/grub
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
VERSION="$(cat /usr/lib/startos/VERSION.txt)"
|
||||||
|
ENVIRONMENT=$(cat /usr/lib/startos/ENVIRONMENT.txt)
|
||||||
|
VERSION_ENV="${VERSION}"
|
||||||
|
if [ -n "${ENVIRONMENT}" ]; then
|
||||||
|
VERSION_ENV="${VERSION} (${ENVIRONMENT})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# set /etc/os-release
|
||||||
|
cat << EOF > /etc/os-release
|
||||||
|
NAME=StartOS
|
||||||
|
VERSION="${VERSION_ENV}"
|
||||||
|
ID=start-os
|
||||||
|
VERSION_ID="${VERSION}"
|
||||||
|
PRETTY_NAME="StartOS v${VERSION_ENV}"
|
||||||
|
HOME_URL="https://start9.com/"
|
||||||
|
SUPPORT_URL="https://docs.start9.com/0.3.5.x/support"
|
||||||
|
BUG_REPORT_URL="https://github.com/Start9Labs/start-os/issues"
|
||||||
|
VARIANT="${ENVIRONMENT}"
|
||||||
|
VARIANT_ID="${ENVIRONMENT}"
|
||||||
|
EOF
|
||||||
|
|
||||||
# set local and remote login prompt
|
# set local and remote login prompt
|
||||||
cat << EOF > /etc/issue
|
cat << EOF > /etc/issue
|
||||||
StartOS v$(cat /usr/lib/startos/VERSION.txt) [\\m] on \\n.local (\\l)
|
StartOS v${VERSION} [\\m] on \\n.local (\\l)
|
||||||
EOF
|
EOF
|
||||||
cat << EOF > /etc/issue.net
|
cat << EOF > /etc/issue.net
|
||||||
StartOS v$(cat /usr/lib/startos/VERSION.txt)
|
StartOS v${VERSION}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# change timezone
|
# change timezone
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
export type GetOsVersionParams = {
|
export type GetOsVersionParams = {
|
||||||
sourceVersion: string | null
|
sourceVersion: string | null
|
||||||
targetVersion: string | null
|
targetVersion: string | null
|
||||||
includePrerelease: boolean | null
|
|
||||||
serverId: string | null
|
serverId: string | null
|
||||||
platform: string | null
|
platform: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,5 @@ export type Manifest = {
|
|||||||
hardwareRequirements: HardwareRequirements
|
hardwareRequirements: HardwareRequirements
|
||||||
gitHash?: GitHash
|
gitHash?: GitHash
|
||||||
osVersion: string
|
osVersion: string
|
||||||
|
sdkVersion: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
// 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 { ProgressUnits } from "./ProgressUnits"
|
||||||
|
|
||||||
export type Progress = null | boolean | { done: number; total: number | null }
|
export type Progress =
|
||||||
|
| null
|
||||||
|
| boolean
|
||||||
|
| { done: number; total: number | null; units: ProgressUnits | null }
|
||||||
|
|||||||
3
sdk/base/lib/osBindings/ProgressUnits.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 ProgressUnits = "bytes" | "steps"
|
||||||
@@ -157,6 +157,7 @@ export { PathOrUrl } from "./PathOrUrl"
|
|||||||
export { Percentage } from "./Percentage"
|
export { Percentage } from "./Percentage"
|
||||||
export { ProcedureId } from "./ProcedureId"
|
export { ProcedureId } from "./ProcedureId"
|
||||||
export { Progress } from "./Progress"
|
export { Progress } from "./Progress"
|
||||||
|
export { ProgressUnits } from "./ProgressUnits"
|
||||||
export { Public } from "./Public"
|
export { Public } from "./Public"
|
||||||
export { RecoverySource } from "./RecoverySource"
|
export { RecoverySource } from "./RecoverySource"
|
||||||
export { RegistryAsset } from "./RegistryAsset"
|
export { RegistryAsset } from "./RegistryAsset"
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export class UseEntrypoint {
|
|||||||
export function isUseEntrypoint(
|
export function isUseEntrypoint(
|
||||||
command: CommandType,
|
command: CommandType,
|
||||||
): command is UseEntrypoint {
|
): command is UseEntrypoint {
|
||||||
return typeof command === "object" && "ENTRYPOINT" in command
|
return typeof command === "object" && "USE_ENTRYPOINT" in command
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CommandType = string | [string, ...string[]] | UseEntrypoint
|
export type CommandType = string | [string, ...string[]] | UseEntrypoint
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import * as patterns from "../../base/lib/util/patterns"
|
|||||||
import { BackupSync, Backups } from "./backup/Backups"
|
import { BackupSync, Backups } from "./backup/Backups"
|
||||||
import { smtpInputSpec } from "../../base/lib/actions/input/inputSpecConstants"
|
import { smtpInputSpec } from "../../base/lib/actions/input/inputSpecConstants"
|
||||||
import { Daemon, Daemons } from "./mainFn/Daemons"
|
import { Daemon, Daemons } from "./mainFn/Daemons"
|
||||||
import { HealthCheck } from "./health/HealthCheck"
|
|
||||||
import { checkPortListening } from "./health/checkFns/checkPortListening"
|
import { checkPortListening } from "./health/checkFns/checkPortListening"
|
||||||
import { checkWebUrl, runHealthScript } from "./health/checkFns"
|
import { checkWebUrl, runHealthScript } from "./health/checkFns"
|
||||||
import { List } from "../../base/lib/actions/input/builder/list"
|
import { List } from "../../base/lib/actions/input/builder/list"
|
||||||
@@ -25,10 +24,7 @@ import { SetupBackupsParams, setupBackups } from "./backup/setupBackups"
|
|||||||
import { setupMain } from "./mainFn"
|
import { setupMain } from "./mainFn"
|
||||||
import { defaultTrigger } from "./trigger/defaultTrigger"
|
import { defaultTrigger } from "./trigger/defaultTrigger"
|
||||||
import { changeOnFirstSuccess, cooldownTrigger } from "./trigger"
|
import { changeOnFirstSuccess, cooldownTrigger } from "./trigger"
|
||||||
import {
|
import { setupServiceInterfaces } from "../../base/lib/interfaces/setupInterfaces"
|
||||||
UpdateServiceInterfaces,
|
|
||||||
setupServiceInterfaces,
|
|
||||||
} from "../../base/lib/interfaces/setupInterfaces"
|
|
||||||
import { successFailure } from "./trigger/successFailure"
|
import { successFailure } from "./trigger/successFailure"
|
||||||
import { MultiHost, Scheme } from "../../base/lib/interfaces/Host"
|
import { MultiHost, Scheme } from "../../base/lib/interfaces/Host"
|
||||||
import { ServiceInterfaceBuilder } from "../../base/lib/interfaces/ServiceInterfaceBuilder"
|
import { ServiceInterfaceBuilder } from "../../base/lib/interfaces/ServiceInterfaceBuilder"
|
||||||
@@ -45,17 +41,13 @@ import { splitCommand } from "./util"
|
|||||||
import { Mounts } from "./mainFn/Mounts"
|
import { Mounts } from "./mainFn/Mounts"
|
||||||
import { setupDependencies } from "../../base/lib/dependencies/setupDependencies"
|
import { setupDependencies } from "../../base/lib/dependencies/setupDependencies"
|
||||||
import * as T from "../../base/lib/types"
|
import * as T from "../../base/lib/types"
|
||||||
import {
|
import { testTypeVersion } from "../../base/lib/exver"
|
||||||
ExtendedVersion,
|
|
||||||
testTypeVersion,
|
|
||||||
VersionRange,
|
|
||||||
} from "../../base/lib/exver"
|
|
||||||
import {
|
import {
|
||||||
CheckDependencies,
|
CheckDependencies,
|
||||||
checkDependencies,
|
checkDependencies,
|
||||||
} from "../../base/lib/dependencies/dependencies"
|
} from "../../base/lib/dependencies/dependencies"
|
||||||
import { GetSslCertificate } from "./util"
|
import { GetSslCertificate } from "./util"
|
||||||
import { getDataVersion, setDataVersion, VersionGraph } from "./version"
|
import { getDataVersion, setDataVersion } from "./version"
|
||||||
import { MaybeFn } from "../../base/lib/actions/setupActions"
|
import { MaybeFn } from "../../base/lib/actions/setupActions"
|
||||||
import { GetInput } from "../../base/lib/actions/setupActions"
|
import { GetInput } from "../../base/lib/actions/setupActions"
|
||||||
import { Run } from "../../base/lib/actions/setupActions"
|
import { Run } from "../../base/lib/actions/setupActions"
|
||||||
@@ -68,7 +60,7 @@ import {
|
|||||||
setupOnUninit,
|
setupOnUninit,
|
||||||
} from "../../base/lib/inits"
|
} from "../../base/lib/inits"
|
||||||
|
|
||||||
export const OSVersion = testTypeVersion("0.4.0-alpha.6")
|
export const OSVersion = testTypeVersion("0.4.0-alpha.7")
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
type AnyNeverCond<T extends any[], Then, Else> =
|
type AnyNeverCond<T extends any[], Then, Else> =
|
||||||
@@ -95,7 +87,7 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
|||||||
| "clearServiceInterfaces"
|
| "clearServiceInterfaces"
|
||||||
| "bind"
|
| "bind"
|
||||||
| "getHostInfo"
|
| "getHostInfo"
|
||||||
type MainUsedEffects = "setMainStatus" | "setHealth"
|
type MainUsedEffects = "setMainStatus"
|
||||||
type CallbackEffects =
|
type CallbackEffects =
|
||||||
| "child"
|
| "child"
|
||||||
| "constRetry"
|
| "constRetry"
|
||||||
@@ -129,6 +121,7 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
|||||||
shutdown: (effects, ...args) => effects.shutdown(...args),
|
shutdown: (effects, ...args) => effects.shutdown(...args),
|
||||||
getDependencies: (effects, ...args) => effects.getDependencies(...args),
|
getDependencies: (effects, ...args) => effects.getDependencies(...args),
|
||||||
getStatus: (effects, ...args) => effects.getStatus(...args),
|
getStatus: (effects, ...args) => effects.getStatus(...args),
|
||||||
|
setHealth: (effects, ...args) => effects.setHealth(...args),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -454,7 +447,6 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
|||||||
hostnames: string[],
|
hostnames: string[],
|
||||||
algorithm?: T.Algorithm,
|
algorithm?: T.Algorithm,
|
||||||
) => new GetSslCertificate(effects, hostnames, algorithm),
|
) => new GetSslCertificate(effects, hostnames, algorithm),
|
||||||
HealthCheck,
|
|
||||||
healthCheck: {
|
healthCheck: {
|
||||||
checkPortListening,
|
checkPortListening,
|
||||||
checkWebUrl,
|
checkWebUrl,
|
||||||
@@ -652,19 +644,12 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
|||||||
successFailure,
|
successFailure,
|
||||||
},
|
},
|
||||||
Mounts: {
|
Mounts: {
|
||||||
of() {
|
of: Mounts.of<Manifest>,
|
||||||
return Mounts.of<Manifest>()
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Backups: {
|
Backups: {
|
||||||
volumes: (
|
ofVolumes: Backups.ofVolumes<Manifest>,
|
||||||
...volumeNames: Array<Manifest["volumes"][number] & string>
|
ofSyncs: Backups.ofSyncs<Manifest>,
|
||||||
) => Backups.withVolumes<Manifest>(...volumeNames),
|
withOptions: Backups.withOptions<Manifest>,
|
||||||
addSets: (
|
|
||||||
...options: BackupSync<Manifest["volumes"][number] & string>[]
|
|
||||||
) => Backups.withSyncs<Manifest>(...options),
|
|
||||||
withOptions: (options?: Partial<SyncOptions>) =>
|
|
||||||
Backups.withOptions<Manifest>(options),
|
|
||||||
},
|
},
|
||||||
InputSpec: {
|
InputSpec: {
|
||||||
/**
|
/**
|
||||||
@@ -705,10 +690,11 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
|||||||
Daemons: {
|
Daemons: {
|
||||||
of(
|
of(
|
||||||
effects: Effects,
|
effects: Effects,
|
||||||
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>,
|
started:
|
||||||
healthChecks: HealthCheck[],
|
| ((onTerm: () => PromiseLike<void>) => PromiseLike<null>)
|
||||||
|
| null,
|
||||||
) {
|
) {
|
||||||
return Daemons.of<Manifest>({ effects, started, healthChecks })
|
return Daemons.of<Manifest>({ effects, started })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
SubContainer: {
|
SubContainer: {
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ export class Backups<M extends T.SDKManifest> implements InitScript {
|
|||||||
private postRestore = async (effects: BackupEffects) => {},
|
private postRestore = async (effects: BackupEffects) => {},
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
static withVolumes<M extends T.SDKManifest = never>(
|
static ofVolumes<M extends T.SDKManifest = never>(
|
||||||
...volumeNames: Array<M["volumes"][number]>
|
...volumeNames: Array<M["volumes"][number]>
|
||||||
): Backups<M> {
|
): Backups<M> {
|
||||||
return Backups.withSyncs(
|
return Backups.ofSyncs(
|
||||||
...volumeNames.map((srcVolume) => ({
|
...volumeNames.map((srcVolume) => ({
|
||||||
dataPath: `/media/startos/volumes/${srcVolume}/` as const,
|
dataPath: `/media/startos/volumes/${srcVolume}/` as const,
|
||||||
backupPath: `/media/startos/backup/volumes/${srcVolume}/` as const,
|
backupPath: `/media/startos/backup/volumes/${srcVolume}/` as const,
|
||||||
@@ -42,7 +42,7 @@ export class Backups<M extends T.SDKManifest> implements InitScript {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static withSyncs<M extends T.SDKManifest = never>(
|
static ofSyncs<M extends T.SDKManifest = never>(
|
||||||
...syncs: BackupSync<M["volumes"][number]>[]
|
...syncs: BackupSync<M["volumes"][number]>[]
|
||||||
) {
|
) {
|
||||||
return syncs.reduce((acc, x) => acc.addSync(x), new Backups<M>())
|
return syncs.reduce((acc, x) => acc.addSync(x), new Backups<M>())
|
||||||
@@ -112,11 +112,9 @@ export class Backups<M extends T.SDKManifest> implements InitScript {
|
|||||||
...options,
|
...options,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addSync(sync: BackupSync<M["volumes"][0]>) {
|
addSync(sync: BackupSync<M["volumes"][0]>) {
|
||||||
this.backupSet.push({
|
this.backupSet.push(sync)
|
||||||
...sync,
|
|
||||||
options: { ...this.options, ...sync.options },
|
|
||||||
})
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function setupBackups<M extends T.SDKManifest>(
|
|||||||
if (options instanceof Function) {
|
if (options instanceof Function) {
|
||||||
backupsFactory = options
|
backupsFactory = options
|
||||||
} else {
|
} else {
|
||||||
backupsFactory = async () => Backups.withVolumes(...options)
|
backupsFactory = async () => Backups.ofVolumes(...options)
|
||||||
}
|
}
|
||||||
const answer: SetupBackupsRes = {
|
const answer: SetupBackupsRes = {
|
||||||
get createBackup() {
|
get createBackup() {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export type HealthCheckParams = {
|
|||||||
trigger?: Trigger
|
trigger?: Trigger
|
||||||
gracePeriod?: number
|
gracePeriod?: number
|
||||||
fn(): Promise<HealthCheckResult> | HealthCheckResult
|
fn(): Promise<HealthCheckResult> | HealthCheckResult
|
||||||
onFirstSuccess?: () => unknown | Promise<unknown>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HealthCheck extends Drop {
|
export class HealthCheck extends Drop {
|
||||||
@@ -32,13 +31,6 @@ export class HealthCheck extends Drop {
|
|||||||
const getCurrentValue = () => this.currentValue
|
const getCurrentValue = () => this.currentValue
|
||||||
const gracePeriod = o.gracePeriod ?? 10_000
|
const gracePeriod = o.gracePeriod ?? 10_000
|
||||||
const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue)
|
const trigger = (o.trigger ?? defaultTrigger)(getCurrentValue)
|
||||||
const triggerFirstSuccess = once(() =>
|
|
||||||
Promise.resolve(
|
|
||||||
"onFirstSuccess" in o && o.onFirstSuccess
|
|
||||||
? o.onFirstSuccess()
|
|
||||||
: undefined,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
const checkStarted = () =>
|
const checkStarted = () =>
|
||||||
[
|
[
|
||||||
this.started,
|
this.started,
|
||||||
@@ -78,9 +70,6 @@ export class HealthCheck extends Drop {
|
|||||||
message: message || "",
|
message: message || "",
|
||||||
})
|
})
|
||||||
this.currentValue.lastResult = result
|
this.currentValue.lastResult = result
|
||||||
await triggerFirstSuccess().catch((err) => {
|
|
||||||
console.error(asError(err))
|
|
||||||
})
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await effects.setHealth({
|
await effects.setHealth({
|
||||||
name: o.name,
|
name: o.name,
|
||||||
|
|||||||
@@ -2,44 +2,49 @@ import { DEFAULT_SIGTERM_TIMEOUT } from "."
|
|||||||
import { NO_TIMEOUT, SIGTERM } from "../../../base/lib/types"
|
import { NO_TIMEOUT, SIGTERM } from "../../../base/lib/types"
|
||||||
|
|
||||||
import * as T from "../../../base/lib/types"
|
import * as T from "../../../base/lib/types"
|
||||||
import { MountOptions, SubContainer } from "../util/SubContainer"
|
import { SubContainer } from "../util/SubContainer"
|
||||||
import { Drop, splitCommand } from "../util"
|
import { Drop, splitCommand } from "../util"
|
||||||
import * as cp from "child_process"
|
import * as cp from "child_process"
|
||||||
import * as fs from "node:fs/promises"
|
import * as fs from "node:fs/promises"
|
||||||
import { Mounts } from "./Mounts"
|
import { DaemonCommandType, ExecCommandOptions, ExecFnOptions } from "./Daemons"
|
||||||
import { DaemonCommandType } from "./Daemons"
|
|
||||||
|
|
||||||
export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
export class CommandController<
|
||||||
|
Manifest extends T.SDKManifest,
|
||||||
|
C extends SubContainer<Manifest> | null,
|
||||||
|
> extends Drop {
|
||||||
private constructor(
|
private constructor(
|
||||||
readonly runningAnswer: Promise<null>,
|
readonly runningAnswer: Promise<null>,
|
||||||
private state: { exited: boolean },
|
private state: { exited: boolean },
|
||||||
private readonly subcontainer: SubContainer<Manifest>,
|
private readonly subcontainer: C,
|
||||||
private process: cp.ChildProcess | AbortController,
|
private process: cp.ChildProcess | AbortController,
|
||||||
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
readonly sigtermTimeout: number = DEFAULT_SIGTERM_TIMEOUT,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
static of<Manifest extends T.SDKManifest>() {
|
static of<
|
||||||
|
Manifest extends T.SDKManifest,
|
||||||
|
C extends SubContainer<Manifest> | null,
|
||||||
|
>() {
|
||||||
return async (
|
return async (
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
subcontainer: SubContainer<Manifest>,
|
subcontainer: C,
|
||||||
exec: DaemonCommandType,
|
exec: DaemonCommandType<Manifest, C>,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
if ("fn" in exec) {
|
if ("fn" in exec) {
|
||||||
const abort = new AbortController()
|
const abort = new AbortController()
|
||||||
const cell: { ctrl: CommandController<Manifest> } = {
|
const cell: { ctrl: CommandController<Manifest, C> } = {
|
||||||
ctrl: new CommandController(
|
ctrl: new CommandController<Manifest, C>(
|
||||||
exec.fn(subcontainer, abort).then(async (command) => {
|
exec.fn(subcontainer, abort).then(async (command) => {
|
||||||
if (command && !abort.signal.aborted) {
|
if (subcontainer && command && !abort.signal.aborted) {
|
||||||
Object.assign(
|
const newCtrl = (
|
||||||
cell.ctrl,
|
await CommandController.of<
|
||||||
await CommandController.of<Manifest>()(
|
Manifest,
|
||||||
effects,
|
SubContainer<Manifest>
|
||||||
subcontainer,
|
>()(effects, subcontainer, command as ExecCommandOptions)
|
||||||
command,
|
).leak()
|
||||||
),
|
|
||||||
)
|
Object.assign(cell.ctrl, newCtrl)
|
||||||
return await cell.ctrl.runningAnswer
|
return await cell.ctrl.runningAnswer
|
||||||
} else {
|
} else {
|
||||||
cell.ctrl.state.exited = true
|
cell.ctrl.state.exited = true
|
||||||
@@ -57,7 +62,7 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
|||||||
let commands: string[]
|
let commands: string[]
|
||||||
if (T.isUseEntrypoint(exec.command)) {
|
if (T.isUseEntrypoint(exec.command)) {
|
||||||
const imageMeta: T.ImageMetadata = await fs
|
const imageMeta: T.ImageMetadata = await fs
|
||||||
.readFile(`/media/startos/images/${subcontainer.imageId}.json`, {
|
.readFile(`/media/startos/images/${subcontainer!.imageId}.json`, {
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
})
|
})
|
||||||
.catch(() => "{}")
|
.catch(() => "{}")
|
||||||
@@ -70,11 +75,11 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
|||||||
|
|
||||||
let childProcess: cp.ChildProcess
|
let childProcess: cp.ChildProcess
|
||||||
if (exec.runAsInit) {
|
if (exec.runAsInit) {
|
||||||
childProcess = await subcontainer.launch(commands, {
|
childProcess = await subcontainer!.launch(commands, {
|
||||||
env: exec.env,
|
env: exec.env,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
childProcess = await subcontainer.spawn(commands, {
|
childProcess = await subcontainer!.spawn(commands, {
|
||||||
env: exec.env,
|
env: exec.env,
|
||||||
stdio: exec.onStdout || exec.onStderr ? "pipe" : "inherit",
|
stdio: exec.onStdout || exec.onStderr ? "pipe" : "inherit",
|
||||||
})
|
})
|
||||||
@@ -108,7 +113,7 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return new CommandController(
|
return new CommandController<Manifest, C>(
|
||||||
answer,
|
answer,
|
||||||
state,
|
state,
|
||||||
subcontainer,
|
subcontainer,
|
||||||
@@ -116,7 +121,7 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
|||||||
exec.sigtermTimeout,
|
exec.sigtermTimeout,
|
||||||
)
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await subcontainer.destroy()
|
await subcontainer?.destroy()
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +149,7 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
|||||||
if (this.process instanceof AbortController) this.process.abort()
|
if (this.process instanceof AbortController) this.process.abort()
|
||||||
else this.process.kill("SIGKILL")
|
else this.process.kill("SIGKILL")
|
||||||
}
|
}
|
||||||
await this.subcontainer.destroy()
|
await this.subcontainer?.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
async term({ signal = SIGTERM, timeout = this.sigtermTimeout } = {}) {
|
||||||
@@ -178,7 +183,7 @@ export class CommandController<Manifest extends T.SDKManifest> extends Drop {
|
|||||||
])
|
])
|
||||||
else await this.runningAnswer
|
else await this.runningAnswer
|
||||||
} finally {
|
} finally {
|
||||||
await this.subcontainer.destroy()
|
await this.subcontainer?.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onDrop(): void {
|
onDrop(): void {
|
||||||
|
|||||||
@@ -17,14 +17,17 @@ const MAX_TIMEOUT_MS = 30000
|
|||||||
* and the others state of running, where it will keep a living running command
|
* and the others state of running, where it will keep a living running command
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class Daemon<Manifest extends T.SDKManifest> extends Drop {
|
export class Daemon<
|
||||||
private commandController: CommandController<Manifest> | null = null
|
Manifest extends T.SDKManifest,
|
||||||
|
C extends SubContainer<Manifest> | null = SubContainer<Manifest> | null,
|
||||||
|
> extends Drop {
|
||||||
|
private commandController: CommandController<Manifest, C> | null = null
|
||||||
private shouldBeRunning = false
|
private shouldBeRunning = false
|
||||||
protected exitedSuccess = false
|
protected exitedSuccess = false
|
||||||
private onExitFns: ((success: boolean) => void)[] = []
|
private onExitFns: ((success: boolean) => void)[] = []
|
||||||
protected constructor(
|
protected constructor(
|
||||||
private subcontainer: SubContainer<Manifest>,
|
private subcontainer: C,
|
||||||
private startCommand: (() => Promise<CommandController<Manifest>>) | null,
|
private startCommand: () => Promise<CommandController<Manifest, C>>,
|
||||||
readonly oneshot: boolean = false,
|
readonly oneshot: boolean = false,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
@@ -33,17 +36,20 @@ export class Daemon<Manifest extends T.SDKManifest> extends Drop {
|
|||||||
return this.oneshot
|
return this.oneshot
|
||||||
}
|
}
|
||||||
static of<Manifest extends T.SDKManifest>() {
|
static of<Manifest extends T.SDKManifest>() {
|
||||||
return async (
|
return async <C extends SubContainer<Manifest> | null>(
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
subcontainer: SubContainer<Manifest>,
|
subcontainer: C,
|
||||||
exec: DaemonCommandType | null,
|
exec: DaemonCommandType<Manifest, C>,
|
||||||
) => {
|
) => {
|
||||||
if (subcontainer.isOwned()) subcontainer = subcontainer.rc()
|
let subc: SubContainer<Manifest> | null = subcontainer
|
||||||
const startCommand = exec
|
if (subcontainer && subcontainer.isOwned()) subc = subcontainer.rc()
|
||||||
? () =>
|
const startCommand = () =>
|
||||||
CommandController.of<Manifest>()(effects, subcontainer.rc(), exec)
|
CommandController.of<Manifest, C>()(
|
||||||
: null
|
effects,
|
||||||
return new Daemon(subcontainer, startCommand)
|
(subc?.rc() ?? null) as C,
|
||||||
|
exec,
|
||||||
|
)
|
||||||
|
return new Daemon(subc, startCommand)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async start() {
|
async start() {
|
||||||
@@ -53,7 +59,7 @@ export class Daemon<Manifest extends T.SDKManifest> extends Drop {
|
|||||||
this.shouldBeRunning = true
|
this.shouldBeRunning = true
|
||||||
let timeoutCounter = 0
|
let timeoutCounter = 0
|
||||||
;(async () => {
|
;(async () => {
|
||||||
while (this.startCommand && this.shouldBeRunning) {
|
while (this.shouldBeRunning) {
|
||||||
if (this.commandController)
|
if (this.commandController)
|
||||||
await this.commandController
|
await this.commandController
|
||||||
.term({})
|
.term({})
|
||||||
@@ -106,10 +112,10 @@ export class Daemon<Manifest extends T.SDKManifest> extends Drop {
|
|||||||
.catch((e) => console.error(asError(e)))
|
.catch((e) => console.error(asError(e)))
|
||||||
this.commandController = null
|
this.commandController = null
|
||||||
this.onExitFns = []
|
this.onExitFns = []
|
||||||
await this.subcontainer.destroy()
|
await this.subcontainer?.destroy()
|
||||||
}
|
}
|
||||||
subcontainerRc(): SubContainerRc<Manifest> {
|
subcontainerRc(): SubContainerRc<Manifest> | null {
|
||||||
return this.subcontainer.rc()
|
return this.subcontainer?.rc() ?? null
|
||||||
}
|
}
|
||||||
onExit(fn: (success: boolean) => void) {
|
onExit(fn: (success: boolean) => void) {
|
||||||
this.onExitFns.push(fn)
|
this.onExitFns.push(fn)
|
||||||
|
|||||||
@@ -37,9 +37,7 @@ export type Ready = {
|
|||||||
})
|
})
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
fn: (
|
fn: () => Promise<HealthCheckResult> | HealthCheckResult
|
||||||
subcontainer: SubContainer<Manifest>,
|
|
||||||
) => Promise<HealthCheckResult> | HealthCheckResult
|
|
||||||
/**
|
/**
|
||||||
* A duration in milliseconds to treat a failing health check as "starting"
|
* A duration in milliseconds to treat a failing health check as "starting"
|
||||||
*
|
*
|
||||||
@@ -65,30 +63,40 @@ export type ExecCommandOptions = {
|
|||||||
onStderr?: (chunk: Buffer | string | any) => void
|
onStderr?: (chunk: Buffer | string | any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExecFnOptions = {
|
export type ExecFnOptions<
|
||||||
|
Manifest extends T.SDKManifest,
|
||||||
|
C extends SubContainer<Manifest> | null,
|
||||||
|
> = {
|
||||||
fn: (
|
fn: (
|
||||||
subcontainer: SubContainer<Manifest>,
|
subcontainer: C,
|
||||||
abort: AbortController,
|
abort: AbortController,
|
||||||
) => Promise<ExecCommandOptions | null>
|
) => Promise<C extends null ? null : ExecCommandOptions | null>
|
||||||
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
|
// Defaults to the DEFAULT_SIGTERM_TIMEOUT = 30_000ms
|
||||||
sigtermTimeout?: number
|
sigtermTimeout?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DaemonCommandType = ExecCommandOptions | ExecFnOptions
|
export type DaemonCommandType<
|
||||||
|
Manifest extends T.SDKManifest,
|
||||||
|
C extends SubContainer<Manifest> | null,
|
||||||
|
> = ExecFnOptions<Manifest, C> | (C extends null ? never : ExecCommandOptions)
|
||||||
|
|
||||||
type NewDaemonParams<Manifest extends T.SDKManifest> = {
|
type NewDaemonParams<
|
||||||
|
Manifest extends T.SDKManifest,
|
||||||
|
C extends SubContainer<Manifest> | null,
|
||||||
|
> = {
|
||||||
/** What to run as the daemon: either an async fn or a commandline command to run in the subcontainer */
|
/** What to run as the daemon: either an async fn or a commandline command to run in the subcontainer */
|
||||||
exec: DaemonCommandType | null
|
exec: DaemonCommandType<Manifest, C>
|
||||||
/** Information about the subcontainer in which the daemon runs */
|
/** The subcontainer in which the daemon runs */
|
||||||
subcontainer: SubContainer<Manifest>
|
subcontainer: C
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddDaemonParams<
|
type AddDaemonParams<
|
||||||
Manifest extends T.SDKManifest,
|
Manifest extends T.SDKManifest,
|
||||||
Ids extends string,
|
Ids extends string,
|
||||||
Id extends string,
|
Id extends string,
|
||||||
|
C extends SubContainer<Manifest> | null,
|
||||||
> = (
|
> = (
|
||||||
| NewDaemonParams<Manifest>
|
| NewDaemonParams<Manifest, C>
|
||||||
| {
|
| {
|
||||||
daemon: Daemon<Manifest>
|
daemon: Daemon<Manifest>
|
||||||
}
|
}
|
||||||
@@ -102,8 +110,15 @@ type AddOneshotParams<
|
|||||||
Manifest extends T.SDKManifest,
|
Manifest extends T.SDKManifest,
|
||||||
Ids extends string,
|
Ids extends string,
|
||||||
Id extends string,
|
Id extends string,
|
||||||
> = NewDaemonParams<Manifest> & {
|
C extends SubContainer<Manifest> | null,
|
||||||
exec: DaemonCommandType
|
> = NewDaemonParams<Manifest, C> & {
|
||||||
|
exec: DaemonCommandType<Manifest, C>
|
||||||
|
/** An array of IDs of prior daemons whose successful initializations are required before this daemon will initialize */
|
||||||
|
requires: Exclude<Ids, Id>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddHealthCheckParams<Ids extends string, Id extends string> = {
|
||||||
|
ready: Ready
|
||||||
/** An array of IDs of prior daemons whose successful initializations are required before this daemon will initialize */
|
/** An array of IDs of prior daemons whose successful initializations are required before this daemon will initialize */
|
||||||
requires: Exclude<Ids, Id>[]
|
requires: Exclude<Ids, Id>[]
|
||||||
}
|
}
|
||||||
@@ -111,7 +126,7 @@ type AddOneshotParams<
|
|||||||
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`
|
type ErrorDuplicateId<Id extends string> = `The id '${Id}' is already used`
|
||||||
|
|
||||||
export const runCommand = <Manifest extends T.SDKManifest>() =>
|
export const runCommand = <Manifest extends T.SDKManifest>() =>
|
||||||
CommandController.of<Manifest>()
|
CommandController.of<Manifest, SubContainer<Manifest>>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for defining and controlling the service daemons
|
* A class for defining and controlling the service daemons
|
||||||
@@ -141,11 +156,12 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
{
|
{
|
||||||
private constructor(
|
private constructor(
|
||||||
readonly effects: T.Effects,
|
readonly effects: T.Effects,
|
||||||
readonly started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>,
|
readonly started:
|
||||||
|
| ((onTerm: () => PromiseLike<void>) => PromiseLike<null>)
|
||||||
|
| null,
|
||||||
readonly daemons: Promise<Daemon<Manifest>>[],
|
readonly daemons: Promise<Daemon<Manifest>>[],
|
||||||
readonly ids: Ids[],
|
readonly ids: Ids[],
|
||||||
readonly healthDaemons: HealthDaemon<Manifest>[],
|
readonly healthDaemons: HealthDaemon<Manifest>[],
|
||||||
readonly healthChecks: HealthCheck[],
|
|
||||||
) {}
|
) {}
|
||||||
/**
|
/**
|
||||||
* Returns an empty new Daemons class with the provided inputSpec.
|
* Returns an empty new Daemons class with the provided inputSpec.
|
||||||
@@ -154,13 +170,18 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
*
|
*
|
||||||
* Daemons run in the order they are defined, with latter daemons being capable of
|
* Daemons run in the order they are defined, with latter daemons being capable of
|
||||||
* depending on prior daemons
|
* depending on prior daemons
|
||||||
* @param options
|
*
|
||||||
|
* @param effects
|
||||||
|
*
|
||||||
|
* @param started
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
static of<Manifest extends T.SDKManifest>(options: {
|
static of<Manifest extends T.SDKManifest>(options: {
|
||||||
effects: T.Effects
|
effects: T.Effects
|
||||||
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>
|
/**
|
||||||
healthChecks: HealthCheck[]
|
* A closure to run once the system is launched. If you are in main, provide the `started` argument you receive from the function arguments
|
||||||
|
*/
|
||||||
|
started: ((onTerm: () => PromiseLike<void>) => PromiseLike<null>) | null
|
||||||
}) {
|
}) {
|
||||||
return new Daemons<Manifest, never>(
|
return new Daemons<Manifest, never>(
|
||||||
options.effects,
|
options.effects,
|
||||||
@@ -168,7 +189,6 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
options.healthChecks,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -177,19 +197,19 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
* @param options
|
* @param options
|
||||||
* @returns a new Daemons object
|
* @returns a new Daemons object
|
||||||
*/
|
*/
|
||||||
addDaemon<Id extends string>(
|
addDaemon<Id extends string, C extends SubContainer<Manifest> | null>(
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
id:
|
id:
|
||||||
"" extends Id ? never :
|
"" extends Id ? never :
|
||||||
ErrorDuplicateId<Id> extends Id ? never :
|
ErrorDuplicateId<Id> extends Id ? never :
|
||||||
Id extends Ids ? ErrorDuplicateId<Id> :
|
Id extends Ids ? ErrorDuplicateId<Id> :
|
||||||
Id,
|
Id,
|
||||||
options: AddDaemonParams<Manifest, Ids, Id>,
|
options: AddDaemonParams<Manifest, Ids, Id, C>,
|
||||||
) {
|
) {
|
||||||
const daemon =
|
const daemon =
|
||||||
"daemon" in options
|
"daemon" in options
|
||||||
? Promise.resolve(options.daemon)
|
? Promise.resolve(options.daemon)
|
||||||
: Daemon.of<Manifest>()(
|
: Daemon.of<Manifest>()<C>(
|
||||||
this.effects,
|
this.effects,
|
||||||
options.subcontainer,
|
options.subcontainer,
|
||||||
options.exec,
|
options.exec,
|
||||||
@@ -201,11 +221,10 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
.filter((x) => x >= 0)
|
.filter((x) => x >= 0)
|
||||||
.map((id) => this.healthDaemons[id]),
|
.map((id) => this.healthDaemons[id]),
|
||||||
id,
|
id,
|
||||||
this.ids,
|
|
||||||
options.ready,
|
options.ready,
|
||||||
this.effects,
|
this.effects,
|
||||||
)
|
)
|
||||||
const daemons = this.daemons.concat(daemon)
|
const daemons = [...this.daemons, daemon]
|
||||||
const ids = [...this.ids, id] as (Ids | Id)[]
|
const ids = [...this.ids, id] as (Ids | Id)[]
|
||||||
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
||||||
return new Daemons<Manifest, Ids | Id>(
|
return new Daemons<Manifest, Ids | Id>(
|
||||||
@@ -214,7 +233,6 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
daemons,
|
daemons,
|
||||||
ids,
|
ids,
|
||||||
healthDaemons,
|
healthDaemons,
|
||||||
this.healthChecks,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +243,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
* @param options
|
* @param options
|
||||||
* @returns a new Daemons object
|
* @returns a new Daemons object
|
||||||
*/
|
*/
|
||||||
addOneshot<Id extends string>(
|
addOneshot<Id extends string, C extends SubContainer<Manifest> | null>(
|
||||||
id: "" extends Id
|
id: "" extends Id
|
||||||
? never
|
? never
|
||||||
: ErrorDuplicateId<Id> extends Id
|
: ErrorDuplicateId<Id> extends Id
|
||||||
@@ -233,9 +251,9 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
: Id extends Ids
|
: Id extends Ids
|
||||||
? ErrorDuplicateId<Id>
|
? ErrorDuplicateId<Id>
|
||||||
: Id,
|
: Id,
|
||||||
options: AddOneshotParams<Manifest, Ids, Id>,
|
options: AddOneshotParams<Manifest, Ids, Id, C>,
|
||||||
) {
|
) {
|
||||||
const daemon = Oneshot.of<Manifest>()(
|
const daemon = Oneshot.of<Manifest>()<C>(
|
||||||
this.effects,
|
this.effects,
|
||||||
options.subcontainer,
|
options.subcontainer,
|
||||||
options.exec,
|
options.exec,
|
||||||
@@ -247,11 +265,10 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
.filter((x) => x >= 0)
|
.filter((x) => x >= 0)
|
||||||
.map((id) => this.healthDaemons[id]),
|
.map((id) => this.healthDaemons[id]),
|
||||||
id,
|
id,
|
||||||
this.ids,
|
|
||||||
"EXIT_SUCCESS",
|
"EXIT_SUCCESS",
|
||||||
this.effects,
|
this.effects,
|
||||||
)
|
)
|
||||||
const daemons = this.daemons.concat(daemon)
|
const daemons = [...this.daemons, daemon]
|
||||||
const ids = [...this.ids, id] as (Ids | Id)[]
|
const ids = [...this.ids, id] as (Ids | Id)[]
|
||||||
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
||||||
return new Daemons<Manifest, Ids | Id>(
|
return new Daemons<Manifest, Ids | Id>(
|
||||||
@@ -260,13 +277,95 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
daemons,
|
daemons,
|
||||||
ids,
|
ids,
|
||||||
healthDaemons,
|
healthDaemons,
|
||||||
this.healthChecks,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the complete list of daemons, including a new HealthCheck defined here
|
||||||
|
* @param id
|
||||||
|
* @param options
|
||||||
|
* @returns a new Daemons object
|
||||||
|
*/
|
||||||
|
addHealthCheck<Id extends string>(
|
||||||
|
id: "" extends Id
|
||||||
|
? never
|
||||||
|
: ErrorDuplicateId<Id> extends Id
|
||||||
|
? never
|
||||||
|
: Id extends Ids
|
||||||
|
? ErrorDuplicateId<Id>
|
||||||
|
: Id,
|
||||||
|
options: AddHealthCheckParams<Ids, Id>,
|
||||||
|
) {
|
||||||
|
const healthDaemon = new HealthDaemon<Manifest>(
|
||||||
|
null,
|
||||||
|
options.requires
|
||||||
|
.map((x) => this.ids.indexOf(x))
|
||||||
|
.filter((x) => x >= 0)
|
||||||
|
.map((id) => this.healthDaemons[id]),
|
||||||
|
id,
|
||||||
|
options.ready,
|
||||||
|
this.effects,
|
||||||
|
)
|
||||||
|
const daemons = [...this.daemons]
|
||||||
|
const ids = [...this.ids, id] as (Ids | Id)[]
|
||||||
|
const healthDaemons = [...this.healthDaemons, healthDaemon]
|
||||||
|
return new Daemons<Manifest, Ids | Id>(
|
||||||
|
this.effects,
|
||||||
|
this.started,
|
||||||
|
daemons,
|
||||||
|
ids,
|
||||||
|
healthDaemons,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the entire system until all daemons have returned `ready`.
|
||||||
|
* @param id
|
||||||
|
* @param options
|
||||||
|
* @returns a new Daemons object
|
||||||
|
*/
|
||||||
|
async runUntilSuccess(timeout: number | null) {
|
||||||
|
let resolve = (_: void) => {}
|
||||||
|
const res = new Promise<void>((res, rej) => {
|
||||||
|
resolve = res
|
||||||
|
if (timeout)
|
||||||
|
setTimeout(() => {
|
||||||
|
const notReady = this.healthDaemons
|
||||||
|
.filter((d) => !d.isReady)
|
||||||
|
.map((d) => d.id)
|
||||||
|
rej(new Error(`Timed out waiting for ${notReady}`))
|
||||||
|
}, timeout)
|
||||||
|
})
|
||||||
|
const daemon = Oneshot.of()(this.effects, null, {
|
||||||
|
fn: async () => {
|
||||||
|
resolve()
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const healthDaemon = new HealthDaemon<Manifest>(
|
||||||
|
daemon,
|
||||||
|
[...this.healthDaemons],
|
||||||
|
"__RUN_UNTIL_SUCCESS",
|
||||||
|
"EXIT_SUCCESS",
|
||||||
|
this.effects,
|
||||||
|
)
|
||||||
|
const daemons = await new Daemons<Manifest, Ids>(
|
||||||
|
this.effects,
|
||||||
|
this.started,
|
||||||
|
[...this.daemons, daemon],
|
||||||
|
this.ids,
|
||||||
|
[...this.healthDaemons, healthDaemon],
|
||||||
|
).build()
|
||||||
|
try {
|
||||||
|
await res
|
||||||
|
} finally {
|
||||||
|
await daemons.term()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
async term() {
|
async term() {
|
||||||
try {
|
try {
|
||||||
this.healthChecks.forEach((health) => health.stop())
|
|
||||||
for (let result of await Promise.allSettled(
|
for (let result of await Promise.allSettled(
|
||||||
this.healthDaemons.map((x) => x.term()),
|
this.healthDaemons.map((x) => x.term()),
|
||||||
)) {
|
)) {
|
||||||
@@ -283,10 +382,7 @@ export class Daemons<Manifest extends T.SDKManifest, Ids extends string>
|
|||||||
for (const daemon of this.healthDaemons) {
|
for (const daemon of this.healthDaemons) {
|
||||||
await daemon.init()
|
await daemon.init()
|
||||||
}
|
}
|
||||||
for (const health of this.healthChecks) {
|
this.started?.(() => this.term())
|
||||||
health.start()
|
|
||||||
}
|
|
||||||
this.started(() => this.term())
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { SetHealth, Effects, SDKManifest } from "../../../base/lib/types"
|
|||||||
import { DEFAULT_SIGTERM_TIMEOUT } from "."
|
import { DEFAULT_SIGTERM_TIMEOUT } from "."
|
||||||
import { asError } from "../../../base/lib/util/asError"
|
import { asError } from "../../../base/lib/util/asError"
|
||||||
import { Oneshot } from "./Oneshot"
|
import { Oneshot } from "./Oneshot"
|
||||||
|
import { SubContainer } from "../util/SubContainer"
|
||||||
|
|
||||||
const oncePromise = <T>() => {
|
const oncePromise = <T>() => {
|
||||||
let resolve: (value: T) => void
|
let resolve: (value: T) => void
|
||||||
@@ -30,16 +31,22 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
|||||||
private running = false
|
private running = false
|
||||||
private started?: number
|
private started?: number
|
||||||
private resolveReady: (() => void) | undefined
|
private resolveReady: (() => void) | undefined
|
||||||
|
private resolvedReady: boolean = false
|
||||||
private readyPromise: Promise<void>
|
private readyPromise: Promise<void>
|
||||||
constructor(
|
constructor(
|
||||||
private readonly daemon: Promise<Daemon<Manifest>>,
|
private readonly daemon: Promise<Daemon<Manifest>> | null,
|
||||||
private readonly dependencies: HealthDaemon<Manifest>[],
|
private readonly dependencies: HealthDaemon<Manifest>[],
|
||||||
readonly id: string,
|
readonly id: string,
|
||||||
readonly ids: string[],
|
|
||||||
readonly ready: Ready | typeof EXIT_SUCCESS,
|
readonly ready: Ready | typeof EXIT_SUCCESS,
|
||||||
readonly effects: Effects,
|
readonly effects: Effects,
|
||||||
) {
|
) {
|
||||||
this.readyPromise = new Promise((resolve) => (this.resolveReady = resolve))
|
this.readyPromise = new Promise(
|
||||||
|
(resolve) =>
|
||||||
|
(this.resolveReady = () => {
|
||||||
|
resolve()
|
||||||
|
this.resolvedReady = true
|
||||||
|
}),
|
||||||
|
)
|
||||||
this.dependencies.forEach((d) => d.addWatcher(() => this.updateStatus()))
|
this.dependencies.forEach((d) => d.addWatcher(() => this.updateStatus()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +59,7 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
|||||||
this.running = false
|
this.running = false
|
||||||
this.healthCheckCleanup?.()
|
this.healthCheckCleanup?.()
|
||||||
|
|
||||||
await this.daemon.then((d) =>
|
await this.daemon?.then((d) =>
|
||||||
d.term({
|
d.term({
|
||||||
...termOptions,
|
...termOptions,
|
||||||
}),
|
}),
|
||||||
@@ -74,11 +81,13 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
|||||||
this.running = newStatus
|
this.running = newStatus
|
||||||
|
|
||||||
if (newStatus) {
|
if (newStatus) {
|
||||||
;(await this.daemon).start()
|
console.debug(`Launching ${this.id}...`)
|
||||||
this.started = performance.now()
|
|
||||||
this.setupHealthCheck()
|
this.setupHealthCheck()
|
||||||
|
;(await this.daemon)?.start()
|
||||||
|
this.started = performance.now()
|
||||||
} else {
|
} else {
|
||||||
;(await this.daemon).stop()
|
console.debug(`Stopping ${this.id}...`)
|
||||||
|
;(await this.daemon)?.stop()
|
||||||
this.turnOffHealthCheck()
|
this.turnOffHealthCheck()
|
||||||
|
|
||||||
this.setHealth({ result: "starting", message: null })
|
this.setHealth({ result: "starting", message: null })
|
||||||
@@ -88,10 +97,19 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
|||||||
private healthCheckCleanup: (() => null) | null = null
|
private healthCheckCleanup: (() => null) | null = null
|
||||||
private turnOffHealthCheck() {
|
private turnOffHealthCheck() {
|
||||||
this.healthCheckCleanup?.()
|
this.healthCheckCleanup?.()
|
||||||
|
|
||||||
|
this.resolvedReady = false
|
||||||
|
this.readyPromise = new Promise(
|
||||||
|
(resolve) =>
|
||||||
|
(this.resolveReady = () => {
|
||||||
|
resolve()
|
||||||
|
this.resolvedReady = true
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
private async setupHealthCheck() {
|
private async setupHealthCheck() {
|
||||||
const daemon = await this.daemon
|
const daemon = await this.daemon
|
||||||
daemon.onExit((success) => {
|
daemon?.onExit((success) => {
|
||||||
if (success && this.ready === "EXIT_SUCCESS") {
|
if (success && this.ready === "EXIT_SUCCESS") {
|
||||||
this.setHealth({ result: "success", message: null })
|
this.setHealth({ result: "success", message: null })
|
||||||
} else if (!success) {
|
} else if (!success) {
|
||||||
@@ -122,28 +140,17 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
|||||||
!res.done;
|
!res.done;
|
||||||
res = await Promise.race([status, trigger.next()])
|
res = await Promise.race([status, trigger.next()])
|
||||||
) {
|
) {
|
||||||
const handle = (await this.daemon).subcontainerRc()
|
const response: HealthCheckResult = await Promise.resolve(
|
||||||
|
this.ready.fn(),
|
||||||
try {
|
).catch((err) => {
|
||||||
const response: HealthCheckResult = await Promise.resolve(
|
console.error(asError(err))
|
||||||
this.ready.fn(handle),
|
return {
|
||||||
).catch((err) => {
|
result: "failure",
|
||||||
console.error(asError(err))
|
message: "message" in err ? err.message : String(err),
|
||||||
return {
|
|
||||||
result: "failure",
|
|
||||||
message: "message" in err ? err.message : String(err),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (
|
|
||||||
this.resolveReady &&
|
|
||||||
(response.result === "success" || response.result === "disabled")
|
|
||||||
) {
|
|
||||||
this.resolveReady()
|
|
||||||
}
|
}
|
||||||
await this.setHealth(response)
|
})
|
||||||
} finally {
|
|
||||||
await handle.destroy()
|
await this.setHealth(response)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).catch((err) => console.error(`Daemon ${this.id} failed: ${err}`))
|
}).catch((err) => console.error(`Daemon ${this.id} failed: ${err}`))
|
||||||
|
|
||||||
@@ -158,10 +165,18 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
|||||||
return this.readyPromise
|
return this.readyPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isReady() {
|
||||||
|
return this.resolvedReady
|
||||||
|
}
|
||||||
|
|
||||||
private async setHealth(health: HealthCheckResult) {
|
private async setHealth(health: HealthCheckResult) {
|
||||||
|
const changed = this._health.result !== health.result
|
||||||
this._health = health
|
this._health = health
|
||||||
|
if (this.resolveReady && health.result === "success") {
|
||||||
|
this.resolveReady()
|
||||||
|
}
|
||||||
|
if (changed) this.healthWatchers.forEach((watcher) => watcher())
|
||||||
if (this.ready === "EXIT_SUCCESS") return
|
if (this.ready === "EXIT_SUCCESS") return
|
||||||
this.healthWatchers.forEach((watcher) => watcher())
|
|
||||||
const display = this.ready.display
|
const display = this.ready.display
|
||||||
if (!display) {
|
if (!display) {
|
||||||
return
|
return
|
||||||
@@ -182,8 +197,18 @@ export class HealthDaemon<Manifest extends SDKManifest> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateStatus() {
|
async updateStatus() {
|
||||||
const healths = this.dependencies.map((d) => d.running && d._health)
|
const healths = this.dependencies.map((d) => ({
|
||||||
this.changeRunning(healths.every((x) => x && x.result === "success"))
|
health: d.running && d._health,
|
||||||
|
id: d.id,
|
||||||
|
}))
|
||||||
|
const waitingOn = healths.filter(
|
||||||
|
(h) => !h.health || h.health.result !== "success",
|
||||||
|
)
|
||||||
|
if (waitingOn.length)
|
||||||
|
console.debug(
|
||||||
|
`daemon ${this.id} waiting on ${waitingOn.map((w) => w.id)}`,
|
||||||
|
)
|
||||||
|
this.changeRunning(!waitingOn.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
|||||||
@@ -10,19 +10,25 @@ import { DaemonCommandType } from "./Daemons"
|
|||||||
* unlike Daemon, does not restart on success
|
* unlike Daemon, does not restart on success
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class Oneshot<Manifest extends T.SDKManifest> extends Daemon<Manifest> {
|
export class Oneshot<
|
||||||
|
Manifest extends T.SDKManifest,
|
||||||
|
C extends SubContainer<Manifest> | null = SubContainer<Manifest> | null,
|
||||||
|
> extends Daemon<Manifest, C> {
|
||||||
static of<Manifest extends T.SDKManifest>() {
|
static of<Manifest extends T.SDKManifest>() {
|
||||||
return async (
|
return async <C extends SubContainer<Manifest> | null>(
|
||||||
effects: T.Effects,
|
effects: T.Effects,
|
||||||
subcontainer: SubContainer<Manifest>,
|
subcontainer: C,
|
||||||
exec: DaemonCommandType | null,
|
exec: DaemonCommandType<Manifest, C>,
|
||||||
) => {
|
) => {
|
||||||
if (subcontainer.isOwned()) subcontainer = subcontainer.rc()
|
let subc: SubContainer<Manifest> | null = subcontainer
|
||||||
const startCommand = exec
|
if (subcontainer && subcontainer.isOwned()) subc = subcontainer.rc()
|
||||||
? () =>
|
const startCommand = () =>
|
||||||
CommandController.of<Manifest>()(effects, subcontainer.rc(), exec)
|
CommandController.of<Manifest, C>()(
|
||||||
: null
|
effects,
|
||||||
return new Oneshot(subcontainer, startCommand, true)
|
(subc?.rc() ?? null) as C,
|
||||||
|
exec,
|
||||||
|
)
|
||||||
|
return new Oneshot<Manifest, C>(subcontainer, startCommand, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
} from "../../../base/lib/types/ManifestTypes"
|
} from "../../../base/lib/types/ManifestTypes"
|
||||||
import { OSVersion } from "../StartSdk"
|
import { OSVersion } from "../StartSdk"
|
||||||
import { VersionGraph } from "../version/VersionGraph"
|
import { VersionGraph } from "../version/VersionGraph"
|
||||||
import { execSync } from "child_process"
|
import { version as sdkVersion } from "../../package.json"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Use this function to define critical information about your package
|
* @description Use this function to define critical information about your package
|
||||||
@@ -55,6 +55,7 @@ export function buildManifest<
|
|||||||
return {
|
return {
|
||||||
...manifest,
|
...manifest,
|
||||||
osVersion: manifest.osVersion ?? OSVersion,
|
osVersion: manifest.osVersion ?? OSVersion,
|
||||||
|
sdkVersion,
|
||||||
version: versions.current.options.version,
|
version: versions.current.options.version,
|
||||||
releaseNotes: versions.current.options.releaseNotes,
|
releaseNotes: versions.current.options.releaseNotes,
|
||||||
satisfies: versions.current.options.satisfies || [],
|
satisfies: versions.current.options.satisfies || [],
|
||||||
|
|||||||
@@ -5,18 +5,18 @@ export abstract class Drop {
|
|||||||
if (weak) weak.drop()
|
if (weak) weak.drop()
|
||||||
})
|
})
|
||||||
private static idCtr: number = 0
|
private static idCtr: number = 0
|
||||||
private id: number
|
private dropId?: number
|
||||||
private ref: { id: number } | WeakRef<{ id: number }>
|
private dropRef?: { id: number } | WeakRef<{ id: number }>
|
||||||
protected constructor() {
|
protected constructor() {
|
||||||
this.id = Drop.idCtr++
|
this.dropId = Drop.idCtr++
|
||||||
this.ref = { id: this.id }
|
this.dropRef = { id: this.dropId }
|
||||||
const weak = this.weak()
|
const weak = this.weak()
|
||||||
Drop.weak[this.id] = weak
|
Drop.weak[this.dropId] = weak
|
||||||
Drop.registry.register(this.ref, this.id, this.ref)
|
Drop.registry.register(this.dropRef, this.dropId, this.dropRef)
|
||||||
|
|
||||||
return new Proxy(this, {
|
return new Proxy(this, {
|
||||||
set(target: any, prop, value) {
|
set(target: any, prop, value) {
|
||||||
if (prop === "ref") return false
|
if (prop === "dropRef" || prop == "dropId") return false
|
||||||
target[prop] = value
|
target[prop] = value
|
||||||
;(weak as any)[prop] = value
|
;(weak as any)[prop] = value
|
||||||
return true
|
return true
|
||||||
@@ -26,13 +26,21 @@ export abstract class Drop {
|
|||||||
protected register() {}
|
protected register() {}
|
||||||
protected weak(): this {
|
protected weak(): this {
|
||||||
const weak = Object.assign(Object.create(Object.getPrototypeOf(this)), this)
|
const weak = Object.assign(Object.create(Object.getPrototypeOf(this)), this)
|
||||||
weak.ref = new WeakRef(this.ref)
|
if (this.dropRef) weak.ref = new WeakRef(this.dropRef)
|
||||||
return weak
|
return weak
|
||||||
}
|
}
|
||||||
abstract onDrop(): void
|
abstract onDrop(): void
|
||||||
drop(): void {
|
drop(): void {
|
||||||
|
if (!this.dropRef || !this.dropId) return
|
||||||
this.onDrop()
|
this.onDrop()
|
||||||
Drop.registry.unregister(this.ref)
|
this.leak()
|
||||||
delete Drop.weak[this.id]
|
}
|
||||||
|
leak(): this {
|
||||||
|
if (!this.dropRef || !this.dropId) return this
|
||||||
|
Drop.registry.unregister(this.dropRef)
|
||||||
|
delete Drop.weak[this.dropId]
|
||||||
|
delete this.dropRef
|
||||||
|
delete this.dropId
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,9 +340,17 @@ export class FileHelper<A> {
|
|||||||
/**
|
/**
|
||||||
* Accepts full structured data and overwrites the existing file on disk if it exists.
|
* Accepts full structured data and overwrites the existing file on disk if it exists.
|
||||||
*/
|
*/
|
||||||
async write(effects: T.Effects, data: T.AllowReadonly<A> | A) {
|
async write(
|
||||||
|
effects: T.Effects,
|
||||||
|
data: T.AllowReadonly<A> | A,
|
||||||
|
options: { allowWriteAfterConst?: boolean } = {},
|
||||||
|
) {
|
||||||
await this.writeFile(this.validate(data))
|
await this.writeFile(this.validate(data))
|
||||||
if (effects.constRetry && this.consts.includes(effects.constRetry))
|
if (
|
||||||
|
!options.allowWriteAfterConst &&
|
||||||
|
effects.constRetry &&
|
||||||
|
this.consts.includes(effects.constRetry)
|
||||||
|
)
|
||||||
throw new Error(`Canceled: write after const: ${this.path}`)
|
throw new Error(`Canceled: write after const: ${this.path}`)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -350,7 +358,11 @@ export class FileHelper<A> {
|
|||||||
/**
|
/**
|
||||||
* Accepts partial structured data and performs a merge with the existing file on disk.
|
* Accepts partial structured data and performs a merge with the existing file on disk.
|
||||||
*/
|
*/
|
||||||
async merge(effects: T.Effects, data: T.AllowReadonly<T.DeepPartial<A>>) {
|
async merge(
|
||||||
|
effects: T.Effects,
|
||||||
|
data: T.AllowReadonly<T.DeepPartial<A>>,
|
||||||
|
options: { allowWriteAfterConst?: boolean } = {},
|
||||||
|
) {
|
||||||
const fileDataRaw = await this.readFileRaw()
|
const fileDataRaw = await this.readFileRaw()
|
||||||
let fileData: any = fileDataRaw === null ? null : this.readData(fileDataRaw)
|
let fileData: any = fileDataRaw === null ? null : this.readData(fileDataRaw)
|
||||||
try {
|
try {
|
||||||
@@ -360,7 +372,11 @@ export class FileHelper<A> {
|
|||||||
const toWrite = this.writeData(mergeData)
|
const toWrite = this.writeData(mergeData)
|
||||||
if (toWrite !== fileDataRaw) {
|
if (toWrite !== fileDataRaw) {
|
||||||
this.writeFile(mergeData)
|
this.writeFile(mergeData)
|
||||||
if (effects.constRetry && this.consts.includes(effects.constRetry)) {
|
if (
|
||||||
|
!options.allowWriteAfterConst &&
|
||||||
|
effects.constRetry &&
|
||||||
|
this.consts.includes(effects.constRetry)
|
||||||
|
) {
|
||||||
const diff = partialDiff(fileData, mergeData as any)
|
const diff = partialDiff(fileData, mergeData as any)
|
||||||
if (!diff) {
|
if (!diff) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
InitScriptOrFn,
|
InitScriptOrFn,
|
||||||
UninitFn,
|
UninitFn,
|
||||||
UninitScript,
|
UninitScript,
|
||||||
|
UninitScriptOrFn,
|
||||||
} from "../../../base/lib/inits"
|
} from "../../../base/lib/inits"
|
||||||
import { Graph, Vertex, once } from "../util"
|
import { Graph, Vertex, once } from "../util"
|
||||||
import { IMPOSSIBLE, VersionInfo } from "./VersionInfo"
|
import { IMPOSSIBLE, VersionInfo } from "./VersionInfo"
|
||||||
@@ -171,11 +172,11 @@ export class VersionGraph<CurrentVersion extends string>
|
|||||||
/**
|
/**
|
||||||
* A script to run only on fresh install
|
* A script to run only on fresh install
|
||||||
*/
|
*/
|
||||||
preInstall?: InitScript | InitFn
|
preInstall?: InitScriptOrFn<"install">
|
||||||
/**
|
/**
|
||||||
* A script to run only on uninstall
|
* A script to run only on uninstall
|
||||||
*/
|
*/
|
||||||
uninstall?: UninitScript | UninitFn
|
uninstall?: UninitScriptOrFn
|
||||||
}) {
|
}) {
|
||||||
return new VersionGraph(
|
return new VersionGraph(
|
||||||
options.current,
|
options.current,
|
||||||
|
|||||||
4
sdk/package/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.27",
|
"version": "0.4.0-beta.30",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.27",
|
"version": "0.4.0-beta.30",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.27",
|
"version": "0.4.0-beta.30",
|
||||||
"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",
|
||||||
@@ -22,14 +22,14 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/Start9Labs/start-sdk.git"
|
"url": "git+https://github.com/Start9Labs/start-os.git"
|
||||||
},
|
},
|
||||||
"author": "Start9 Labs",
|
"author": "Start9 Labs",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/Start9Labs/start-sdk/issues"
|
"url": "https://github.com/Start9Labs/start-os/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/Start9Labs/start-sdk#readme",
|
"homepage": "https://github.com/Start9Labs/start-os#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"mime": "^4.0.7",
|
"mime": "^4.0.7",
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"outDir": "../dist",
|
"outDir": "../dist",
|
||||||
"target": "es2021"
|
"target": "es2021",
|
||||||
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"include": ["lib/**/*", "../base/lib/util/Hostname.ts"],
|
"include": ["lib/**/*", "../base/lib/util/Hostname.ts"],
|
||||||
"exclude": ["lib/**/*.spec.ts", "lib/**/*.gen.ts", "list", "node_modules"]
|
"exclude": ["lib/**/*.spec.ts", "lib/**/*.gen.ts", "list", "node_modules"]
|
||||||
|
|||||||
4
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.4.0-alpha.6",
|
"version": "0.4.0-alpha.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.4.0-alpha.6",
|
"version": "0.4.0-alpha.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^19.2.11",
|
"@angular/animations": "^19.2.11",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "startos-ui",
|
"name": "startos-ui",
|
||||||
"version": "0.4.0-alpha.6",
|
"version": "0.4.0-alpha.7",
|
||||||
"author": "Start9 Labs, Inc",
|
"author": "Start9 Labs, Inc",
|
||||||
"homepage": "https://start9.com/",
|
"homepage": "https://start9.com/",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
@@ -14,7 +14,14 @@
|
|||||||
<meta name="format-detection" content="telephone=no" />
|
<meta name="format-detection" content="telephone=no" />
|
||||||
<meta name="msapplication-tap-highlight" content="no" />
|
<meta name="msapplication-tap-highlight" content="no" />
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="assets/icon/favicon.ico" />
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
href="/assets/icons/favicon-96x96.png"
|
||||||
|
sizes="96x96"
|
||||||
|
/>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/assets/icons/favicon.svg" />
|
||||||
|
<link rel="shortcut icon" href="/assets/icons/favicon.ico" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
@@ -15,7 +15,14 @@
|
|||||||
<script>
|
<script>
|
||||||
var global = window
|
var global = window
|
||||||
</script>
|
</script>
|
||||||
<link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico" />
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
href="/assets/icons/favicon-96x96.png"
|
||||||
|
sizes="96x96"
|
||||||
|
/>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/assets/icons/favicon.svg" />
|
||||||
|
<link rel="shortcut icon" href="/assets/icons/favicon.ico" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "StartOS",
|
|
||||||
"short_name": "StartOS",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "/assets/icons/web-app-manifest-192x192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "maskable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/assets/icons/web-app-manifest-512x512.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "maskable"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff",
|
|
||||||
"display": "standalone"
|
|
||||||
}
|
|
||||||
BIN
web/projects/shared/assets/icons/startos-192x192.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
web/projects/shared/assets/icons/startos-512x512.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 24 KiB |
@@ -1,5 +0,0 @@
|
|||||||
<svg width="1369" height="1369" viewBox="0 0 1369 1369" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M245.229 1154.11C218.012 1203.36 279.4 1235.04 304.523 1188.79C315.173 1169.19 641.573 388.267 681.128 296.285L1061.75 1185.9C1087.02 1239.85 1150.2 1200.35 1126.87 1149.29L719.161 220.202C699.384 177.981 658.308 177.981 640.052 221.71C638.53 224.726 260.899 1125.74 245.229 1154.11Z" fill="#F0F0F0"/>
|
|
||||||
<path d="M173.678 1079.24C99.1497 984.791 52.8134 871.532 39.9236 752.308C27.0337 633.084 48.106 512.665 100.751 404.708C153.396 296.75 235.507 205.573 337.773 141.516C440.038 77.4585 558.367 43.0845 679.34 42.2916C800.313 41.4988 919.089 74.3187 1022.2 137.03C1125.31 199.741 1208.63 289.834 1262.71 397.092C1316.79 504.35 1339.47 624.482 1328.17 743.864C1316.88 863.247 1272.05 977.103 1198.79 1072.52L1197.54 1071.58C1270.62 976.389 1315.33 862.81 1326.6 743.718C1337.87 624.627 1315.25 504.788 1261.3 397.791C1207.35 290.794 1124.24 200.921 1021.38 138.362C918.516 75.804 800.028 43.064 679.35 43.8549C558.672 44.6459 440.632 78.9361 338.615 142.837C236.599 206.738 154.688 297.693 102.171 405.388C49.6544 513.082 28.6335 633.208 41.4919 752.141C54.3504 871.075 100.574 984.058 174.92 1078.28L173.678 1079.24Z" stroke="#F0F0F0" stroke-width="65.4186" stroke-linejoin="round"/>
|
|
||||||
<path d="M393.951 1258.16C484.768 1302.62 583.891 1326.42 685.181 1325.58C786.471 1324.73 886.158 1302.21 976.206 1256.23L976.206 1254.3C886.377 1300.17 786.211 1323.17 685.167 1324.01C584.124 1324.86 484.546 1300.59 393.951 1256.23L393.951 1258.16Z" stroke="#F0F0F0" stroke-width="65.4186" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,7 +0,0 @@
|
|||||||
<svg width="32" height="32" viewBox="0 0 34 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="4" y="4.78958" width="26.4999" height="26.4999" rx="3.53333" stroke="black" stroke-width="1.76666" stroke-linejoin="round"/>
|
|
||||||
<rect x="8.47266" y="9.2063" width="7" height="7" rx="0.883332" fill="black"/>
|
|
||||||
<rect x="8.47266" y="18.9231" width="7" height="7.94998" rx="0.883332" fill="black"/>
|
|
||||||
<rect x="19.0723" y="22.4563" width="7" height="4.41666" rx="0.883332" fill="black"/>
|
|
||||||
<rect x="19.0723" y="9.2063" width="7" height="10.6" rx="0.883332" fill="black"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 580 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path d="M410.47 279.2c-5-11.5-12.7-21.6-28.1-30.1a98.15 98.15 0 00-25.4-10 62.22 62.22 0 0016.3-11 56.37 56.37 0 0015.6-23.3 77.11 77.11 0 003.5-28.2c-1.1-16.8-4.4-33.1-13.2-44.8s-21.2-20.7-37.6-27c-12.6-4.8-25.5-7.8-45.5-8.9V32h-40v64h-32V32h-41v64H96v48h27.87c8.7 0 14.6.8 17.6 2.3a13.22 13.22 0 016.5 6c1.3 2.5 1.9 8.4 1.9 17.5V343c0 9-.6 14.8-1.9 17.4s-2 4.9-5.1 6.3-3.2 1.3-11.8 1.3h-26.4L96 416h87v64h41v-64h32v64h40v-64.4c26-1.3 44.5-4.7 59.4-10.3 19.3-7.2 34.1-17.7 44.7-31.5s14-34.9 14.93-51.2c.67-14.5-.03-33.2-4.56-43.4zM224 150h32v74h-32zm0 212v-90h32v90zm72-208.1c6 2.5 9.9 7.5 13.8 12.7 4.3 5.7 6.5 13.3 6.5 21.4 0 7.8-2.9 14.5-7.5 20.5-3.8 4.9-6.8 8.3-12.8 11.1zm28.8 186.7c-7.8 6.9-12.3 10.1-22.1 13.8a56.06 56.06 0 01-6.7 1.9v-82.8a40.74 40.74 0 0111.3 3.4c7.8 3.3 15.2 6.9 19.8 13.2a43.82 43.82 0 018 24.7c-.03 10.9-2.83 19.2-10.33 25.8z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 943 B |
|
Before Width: | Height: | Size: 282 KiB |
|
Before Width: | Height: | Size: 329 KiB |
|
Before Width: | Height: | Size: 1.7 MiB |
@@ -521,4 +521,5 @@ export default {
|
|||||||
519: 'Um Clearnet-Domains zu veröffentlichen, musst du oben auf „Öffentlich machen“ klicken.',
|
519: 'Um Clearnet-Domains zu veröffentlichen, musst du oben auf „Öffentlich machen“ klicken.',
|
||||||
520: 'Update verfügbar',
|
520: 'Update verfügbar',
|
||||||
521: 'Um das Problem zu beheben, siehe',
|
521: 'Um das Problem zu beheben, siehe',
|
||||||
|
522: 'SDK Version',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -520,4 +520,5 @@ export const ENGLISH = {
|
|||||||
'To publish clearnet domains, you must click "Make Public", above.': 519,
|
'To publish clearnet domains, you must click "Make Public", above.': 519,
|
||||||
'Update available': 520,
|
'Update available': 520,
|
||||||
'To resolve the issue, refer to': 521,
|
'To resolve the issue, refer to': 521,
|
||||||
|
'SDK Version': 522,
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -521,4 +521,5 @@ export default {
|
|||||||
519: 'Para publicar dominios en clearnet, debes hacer clic en "Hacer público" arriba.',
|
519: 'Para publicar dominios en clearnet, debes hacer clic en "Hacer público" arriba.',
|
||||||
520: 'Actualización disponible',
|
520: 'Actualización disponible',
|
||||||
521: 'Para resolver el problema, consulta',
|
521: 'Para resolver el problema, consulta',
|
||||||
|
522: 'Versión de SDK',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -521,4 +521,5 @@ export default {
|
|||||||
519: 'Pour publier des domaines clearnet, vous devez cliquer sur « Rendre public » ci-dessus.',
|
519: 'Pour publier des domaines clearnet, vous devez cliquer sur « Rendre public » ci-dessus.',
|
||||||
520: 'Mise à jour disponible',
|
520: 'Mise à jour disponible',
|
||||||
521: 'Pour résoudre le problème, consultez',
|
521: 'Pour résoudre le problème, consultez',
|
||||||
|
522: 'Version de SDK',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -521,4 +521,5 @@ export default {
|
|||||||
519: 'Aby opublikować domeny w clearnet, kliknij „Upublicznij” powyżej.',
|
519: 'Aby opublikować domeny w clearnet, kliknij „Upublicznij” powyżej.',
|
||||||
520: 'Aktualizacja dostępna',
|
520: 'Aktualizacja dostępna',
|
||||||
521: 'Aby rozwiązać problem, zapoznaj się z',
|
521: 'Aby rozwiązać problem, zapoznaj się z',
|
||||||
|
522: 'Wersja SDK',
|
||||||
} satisfies i18n
|
} satisfies i18n
|
||||||
|
|||||||
@@ -12,15 +12,10 @@ export function formatProgress({ phases, overall }: T.FullProgress): {
|
|||||||
p,
|
p,
|
||||||
): p is {
|
): p is {
|
||||||
name: string
|
name: string
|
||||||
progress:
|
progress: false | ProgressDetails
|
||||||
| false
|
|
||||||
| {
|
|
||||||
done: number
|
|
||||||
total: number | null
|
|
||||||
}
|
|
||||||
} => p.progress !== true && p.progress !== null,
|
} => p.progress !== true && p.progress !== null,
|
||||||
)
|
)
|
||||||
.map(p => `<b>${p.name}</b>${getPhaseBytes(p.progress)}`)
|
.map(p => `<b>${p.name}</b>${getDetails(p.progress)}`)
|
||||||
.join(', '),
|
.join(', '),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,13 +30,14 @@ function getDecimal(progress: T.Progress): number {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPhaseBytes(
|
function getDetails(progress: false | ProgressDetails) {
|
||||||
progress:
|
return progress
|
||||||
| false
|
? `: ${progress.done}/${progress.total} ${progress.units || ''}`
|
||||||
| {
|
: ''
|
||||||
done: number
|
}
|
||||||
total: number | null
|
|
||||||
},
|
type ProgressDetails = {
|
||||||
) {
|
done: number
|
||||||
return progress ? `: ${progress.done}/${progress.total}` : ''
|
total: number | null
|
||||||
|
units: T.ProgressUnits | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,6 +202,11 @@ export class InterfaceClearnetComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loader = this.loader.open('Removing').subscribe()
|
const loader = this.loader.open('Removing').subscribe()
|
||||||
|
|
||||||
|
if (!/^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(url)) {
|
||||||
|
url = 'http://' + url
|
||||||
|
}
|
||||||
|
|
||||||
const params = { domain: new URL(url).hostname }
|
const params = { domain: new URL(url).hostname }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
|||||||
<span tuiSubtitle class="g-warning">
|
<span tuiSubtitle class="g-warning">
|
||||||
{{ error | i18n }}
|
{{ error | i18n }}
|
||||||
@if (getHealthCheckName(d.key); as healthCheckName) {
|
@if (getHealthCheckName(d.key); as healthCheckName) {
|
||||||
: {{ getHealthCheckName }}
|
: {{ healthCheckName }}
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
@@ -81,9 +81,17 @@ export default class ServiceAboutRoute {
|
|||||||
icon: '@tui.copy',
|
icon: '@tui.copy',
|
||||||
action: () => this.copyService.copy(manifest.version),
|
action: () => this.copyService.copy(manifest.version),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'SDK Version',
|
||||||
|
value: manifest.sdkVersion || '-',
|
||||||
|
icon: manifest.sdkVersion ? '@tui.copy' : '',
|
||||||
|
action: () =>
|
||||||
|
manifest.sdkVersion &&
|
||||||
|
this.copyService.copy(manifest.sdkVersion),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Git Hash',
|
name: 'Git Hash',
|
||||||
value: manifest.gitHash || 'Unknown',
|
value: manifest.gitHash || '-',
|
||||||
icon: manifest.gitHash ? '@tui.copy' : '',
|
icon: manifest.gitHash ? '@tui.copy' : '',
|
||||||
action: () =>
|
action: () =>
|
||||||
manifest.gitHash && this.copyService.copy(manifest.gitHash),
|
manifest.gitHash && this.copyService.copy(manifest.gitHash),
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export namespace Mock {
|
|||||||
squashfs: {
|
squashfs: {
|
||||||
aarch64: {
|
aarch64: {
|
||||||
publishedAt: '2025-04-21T20:58:48.140749883Z',
|
publishedAt: '2025-04-21T20:58:48.140749883Z',
|
||||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.6/startos-0.4.0-alpha.6-33ae46f~dev_aarch64.squashfs',
|
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.7/startos-0.4.0-alpha.7-33ae46f~dev_aarch64.squashfs',
|
||||||
commitment: {
|
commitment: {
|
||||||
hash: '4elBFVkd/r8hNadKmKtLIs42CoPltMvKe2z3LRqkphk=',
|
hash: '4elBFVkd/r8hNadKmKtLIs42CoPltMvKe2z3LRqkphk=',
|
||||||
size: 1343500288,
|
size: 1343500288,
|
||||||
@@ -122,7 +122,7 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
'aarch64-nonfree': {
|
'aarch64-nonfree': {
|
||||||
publishedAt: '2025-04-21T21:07:00.249285116Z',
|
publishedAt: '2025-04-21T21:07:00.249285116Z',
|
||||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.6/startos-0.4.0-alpha.6-33ae46f~dev_aarch64-nonfree.squashfs',
|
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.7/startos-0.4.0-alpha.7-33ae46f~dev_aarch64-nonfree.squashfs',
|
||||||
commitment: {
|
commitment: {
|
||||||
hash: 'MrCEi4jxbmPS7zAiGk/JSKlMsiuKqQy6RbYOxlGHOIQ=',
|
hash: 'MrCEi4jxbmPS7zAiGk/JSKlMsiuKqQy6RbYOxlGHOIQ=',
|
||||||
size: 1653075968,
|
size: 1653075968,
|
||||||
@@ -134,7 +134,7 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
raspberrypi: {
|
raspberrypi: {
|
||||||
publishedAt: '2025-04-21T21:16:12.933319237Z',
|
publishedAt: '2025-04-21T21:16:12.933319237Z',
|
||||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.6/startos-0.4.0-alpha.6-33ae46f~dev_raspberrypi.squashfs',
|
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.7/startos-0.4.0-alpha.7-33ae46f~dev_raspberrypi.squashfs',
|
||||||
commitment: {
|
commitment: {
|
||||||
hash: '/XTVQRCqY3RK544PgitlKu7UplXjkmzWoXUh2E4HCw0=',
|
hash: '/XTVQRCqY3RK544PgitlKu7UplXjkmzWoXUh2E4HCw0=',
|
||||||
size: 1490731008,
|
size: 1490731008,
|
||||||
@@ -146,7 +146,7 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
x86_64: {
|
x86_64: {
|
||||||
publishedAt: '2025-04-21T21:14:20.246908903Z',
|
publishedAt: '2025-04-21T21:14:20.246908903Z',
|
||||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.6/startos-0.4.0-alpha.6-33ae46f~dev_x86_64.squashfs',
|
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.7/startos-0.4.0-alpha.7-33ae46f~dev_x86_64.squashfs',
|
||||||
commitment: {
|
commitment: {
|
||||||
hash: '/6romKTVQGSaOU7FqSZdw0kFyd7P+NBSYNwM3q7Fe44=',
|
hash: '/6romKTVQGSaOU7FqSZdw0kFyd7P+NBSYNwM3q7Fe44=',
|
||||||
size: 1411657728,
|
size: 1411657728,
|
||||||
@@ -158,7 +158,7 @@ export namespace Mock {
|
|||||||
},
|
},
|
||||||
'x86_64-nonfree': {
|
'x86_64-nonfree': {
|
||||||
publishedAt: '2025-04-21T21:15:17.955265284Z',
|
publishedAt: '2025-04-21T21:15:17.955265284Z',
|
||||||
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.6/startos-0.4.0-alpha.6-33ae46f~dev_x86_64-nonfree.squashfs',
|
url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.7/startos-0.4.0-alpha.7-33ae46f~dev_x86_64-nonfree.squashfs',
|
||||||
commitment: {
|
commitment: {
|
||||||
hash: 'HCRq9sr/0t85pMdrEgNBeM4x11zVKHszGnD1GDyZbSE=',
|
hash: 'HCRq9sr/0t85pMdrEgNBeM4x11zVKHszGnD1GDyZbSE=',
|
||||||
size: 1731035136,
|
size: 1731035136,
|
||||||
@@ -226,6 +226,7 @@ export namespace Mock {
|
|||||||
stop: null,
|
stop: null,
|
||||||
},
|
},
|
||||||
osVersion: '0.2.12',
|
osVersion: '0.2.12',
|
||||||
|
sdkVersion: '0.4.0',
|
||||||
dependencies: {},
|
dependencies: {},
|
||||||
images: {
|
images: {
|
||||||
main: {
|
main: {
|
||||||
@@ -270,6 +271,7 @@ export namespace Mock {
|
|||||||
stop: null,
|
stop: null,
|
||||||
},
|
},
|
||||||
osVersion: '0.2.12',
|
osVersion: '0.2.12',
|
||||||
|
sdkVersion: '0.4.0',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
description: 'LND needs bitcoin to live.',
|
description: 'LND needs bitcoin to live.',
|
||||||
@@ -325,6 +327,7 @@ export namespace Mock {
|
|||||||
stop: null,
|
stop: null,
|
||||||
},
|
},
|
||||||
osVersion: '0.2.12',
|
osVersion: '0.2.12',
|
||||||
|
sdkVersion: '0.4.0',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
description: 'Bitcoin Proxy requires a Bitcoin node.',
|
description: 'Bitcoin Proxy requires a Bitcoin node.',
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ const PROGRESS: T.FullProgress = {
|
|||||||
overall: {
|
overall: {
|
||||||
done: 0,
|
done: 0,
|
||||||
total: 120,
|
total: 120,
|
||||||
|
units: 'bytes',
|
||||||
},
|
},
|
||||||
phases: [
|
phases: [
|
||||||
{
|
{
|
||||||
@@ -39,6 +40,7 @@ const PROGRESS: T.FullProgress = {
|
|||||||
progress: {
|
progress: {
|
||||||
done: 0,
|
done: 0,
|
||||||
total: 40,
|
total: 40,
|
||||||
|
units: 'bytes',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { enableProdMode } from '@angular/core'
|
import { enableProdMode } from '@angular/core'
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
||||||
|
|
||||||
import { AppModule } from './app/app.module'
|
import { AppModule } from './app/app.module'
|
||||||
import { environment } from './environments/environment'
|
import { environment } from './environments/environment'
|
||||||
|
|
||||||
; (window as any).global = window
|
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
enableProdMode()
|
enableProdMode()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "StartOS",
|
"name": "StartOS",
|
||||||
"short_name": "StartOS",
|
"background_color": "#f0f0f0",
|
||||||
"theme_color": "#ff5b71",
|
|
||||||
"background_color": "#1e1e1e",
|
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"scope": ".",
|
"start_url": "./",
|
||||||
"start_url": "/?version=036",
|
|
||||||
"id": "/?version=036",
|
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/assets/icons/web-app-manifest-192x192.png",
|
"src": "/assets/icons/startos-192x192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192"
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "any"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/assets/icons/web-app-manifest-512x512.png",
|
"src": "/assets/icons/startos-512x512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512"
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "any"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||