mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
add start/stop/restart to effects
fix: add a default always to the get status chore: Only do updates when the thing is installed. chore: Make the thing buildable for testing chore: Add in the debugging chore: Remove the bluj tracing chore: Fix the build Chore: Make these fn's instead of allways ran. chore: Fix the testing fix: The stopping/ restarting service fix: Fix the restarting.
This commit is contained in:
@@ -81,7 +81,7 @@ impl CliContext {
|
|||||||
.chain(std::iter::once(Path::new(crate::util::config::CONFIG_PATH))),
|
.chain(std::iter::once(Path::new(crate::util::config::CONFIG_PATH))),
|
||||||
)?;
|
)?;
|
||||||
let mut url = if let Some(host) = matches.value_of("host") {
|
let mut url = if let Some(host) = matches.value_of("host") {
|
||||||
Url::parse host.parse()?
|
host.parse()?
|
||||||
} else if let Some(host) = base.host {
|
} else if let Some(host) = base.host {
|
||||||
host
|
host
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -173,10 +173,28 @@ pub async fn stop_dry(
|
|||||||
pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result<MainStatus, Error> {
|
pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result<MainStatus, Error> {
|
||||||
let mut db = ctx.db.handle();
|
let mut db = ctx.db.handle();
|
||||||
let mut tx = db.begin().await?;
|
let mut tx = db.begin().await?;
|
||||||
|
let version = crate::db::DatabaseModel::new()
|
||||||
|
.package_data()
|
||||||
|
.idx_model(&id)
|
||||||
|
.expect(&mut tx)
|
||||||
|
.await?
|
||||||
|
.installed()
|
||||||
|
.expect(&mut tx)
|
||||||
|
.await?
|
||||||
|
.manifest()
|
||||||
|
.version()
|
||||||
|
.get(&mut tx)
|
||||||
|
.await?
|
||||||
|
.clone();
|
||||||
|
|
||||||
let last_statuts = stop_common(&mut tx, &id, &mut BTreeMap::new()).await?;
|
let last_statuts = stop_common(&mut tx, &id, &mut BTreeMap::new()).await?;
|
||||||
|
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
|
ctx.managers
|
||||||
|
.get(&(id, version))
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))?
|
||||||
|
.stop();
|
||||||
|
|
||||||
Ok(last_statuts)
|
Ok(last_statuts)
|
||||||
}
|
}
|
||||||
@@ -185,23 +203,28 @@ pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result<MainStatus, Err
|
|||||||
pub async fn restart(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(), Error> {
|
pub async fn restart(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(), Error> {
|
||||||
let mut db = ctx.db.handle();
|
let mut db = ctx.db.handle();
|
||||||
let mut tx = db.begin().await?;
|
let mut tx = db.begin().await?;
|
||||||
|
let version = crate::db::DatabaseModel::new()
|
||||||
let mut status = crate::db::DatabaseModel::new()
|
|
||||||
.package_data()
|
.package_data()
|
||||||
.idx_model(&id)
|
.idx_model(&id)
|
||||||
.and_then(|pde| pde.installed())
|
.expect(&mut tx)
|
||||||
.map(|i| i.status().main())
|
.await?
|
||||||
.get_mut(&mut tx)
|
.installed()
|
||||||
.await?;
|
.expect(&mut tx)
|
||||||
if !matches!(&*status, Some(MainStatus::Running { .. })) {
|
.await?
|
||||||
return Err(Error::new(
|
.manifest()
|
||||||
eyre!("{} is not running", id),
|
.version()
|
||||||
crate::ErrorKind::InvalidRequest,
|
.get(&mut tx)
|
||||||
));
|
.await?
|
||||||
}
|
.clone();
|
||||||
*status = Some(MainStatus::Restarting);
|
|
||||||
status.save(&mut tx).await?;
|
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
|
|
||||||
|
ctx.managers
|
||||||
|
.get(&(id, version))
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))?
|
||||||
|
.restart()
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1047,7 +1047,7 @@ pub fn heal_transitive<'a, Db: DbHandle>(
|
|||||||
|
|
||||||
pub async fn reconfigure_dependents_with_live_pointers(
|
pub async fn reconfigure_dependents_with_live_pointers(
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
mut tx: impl DbHandle,
|
tx: impl DbHandle,
|
||||||
receipts: &ConfigReceipts,
|
receipts: &ConfigReceipts,
|
||||||
pde: &InstalledPackageDataEntry,
|
pde: &InstalledPackageDataEntry,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use patch_db::{DbHandle, LockReceipt, LockType};
|
use patch_db::{DbHandle, LockReceipt, LockType};
|
||||||
|
|||||||
@@ -147,7 +147,6 @@ impl OsApi for Manager {
|
|||||||
.create_service(self.seed.manifest.id.clone(), ip)
|
.create_service(self.seed.manifest.id.clone(), ip)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| eyre!("Could not get to net controller: {e:?}"))?;
|
.map_err(|e| eyre!("Could not get to net controller: {e:?}"))?;
|
||||||
let mut secrets = self.seed.ctx.secret_store.acquire().await?;
|
|
||||||
|
|
||||||
svc.remove_lan(id, external)
|
svc.remove_lan(id, external)
|
||||||
.await
|
.await
|
||||||
@@ -165,7 +164,6 @@ impl OsApi for Manager {
|
|||||||
.create_service(self.seed.manifest.id.clone(), ip)
|
.create_service(self.seed.manifest.id.clone(), ip)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| eyre!("Could not get to net controller: {e:?}"))?;
|
.map_err(|e| eyre!("Could not get to net controller: {e:?}"))?;
|
||||||
let mut secrets = self.seed.ctx.secret_store.acquire().await?;
|
|
||||||
|
|
||||||
svc.remove_tor(id, external)
|
svc.remove_tor(id, external)
|
||||||
.await
|
.await
|
||||||
@@ -173,30 +171,26 @@ impl OsApi for Manager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_started(&self) -> Result<(), Report> {
|
fn set_started(&self) {
|
||||||
self.manage_container
|
self.manage_container
|
||||||
.current_state
|
.current_state
|
||||||
.send(StartStop::Start)
|
.send(StartStop::Start)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default()
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn restart(&self) -> Result<(), Report> {
|
async fn restart(&self) {
|
||||||
self.perform_restart().await;
|
self.perform_restart().await
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start(&self) -> Result<(), Report> {
|
async fn start(&self) {
|
||||||
self.manage_container
|
self.manage_container
|
||||||
.wait_for_desired(StartStop::Start)
|
.wait_for_desired(StartStop::Start)
|
||||||
.await;
|
.await
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn stop(&self) -> Result<(), Report> {
|
async fn stop(&self) {
|
||||||
self.manage_container
|
self.manage_container
|
||||||
.wait_for_desired(StartStop::Stop)
|
.wait_for_desired(StartStop::Stop)
|
||||||
.await;
|
.await
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use futures::FutureExt;
|
||||||
use patch_db::PatchDbHandle;
|
use patch_db::PatchDbHandle;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use tokio::sync::watch::Sender;
|
use tokio::sync::watch::Sender;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::start_stop::StartStop;
|
use super::start_stop::StartStop;
|
||||||
use super::{manager_seed, run_main, ManagerPersistentContainer, RunMainResult};
|
use super::{manager_seed, run_main, ManagerPersistentContainer, RunMainResult};
|
||||||
|
use crate::procedure::NoOutput;
|
||||||
use crate::s9pk::manifest::Manifest;
|
use crate::s9pk::manifest::Manifest;
|
||||||
use crate::status::MainStatus;
|
use crate::status::MainStatus;
|
||||||
use crate::util::{GeneralBoxedGuard, NonDetachingJoinHandle};
|
use crate::util::{GeneralBoxedGuard, NonDetachingJoinHandle};
|
||||||
@@ -15,10 +18,10 @@ use crate::Error;
|
|||||||
pub type ManageContainerOverride = Arc<watch::Sender<Option<MainStatus>>>;
|
pub type ManageContainerOverride = Arc<watch::Sender<Option<MainStatus>>>;
|
||||||
|
|
||||||
pub struct ManageContainer {
|
pub struct ManageContainer {
|
||||||
current_state: Arc<watch::Sender<StartStop>>,
|
pub(super) current_state: Arc<watch::Sender<StartStop>>,
|
||||||
desired_state: Arc<watch::Sender<StartStop>>,
|
pub(super) desired_state: Arc<watch::Sender<StartStop>>,
|
||||||
service: NonDetachingJoinHandle<()>,
|
_service: NonDetachingJoinHandle<()>,
|
||||||
save_state: NonDetachingJoinHandle<()>,
|
_save_state: NonDetachingJoinHandle<()>,
|
||||||
override_main_status: ManageContainerOverride,
|
override_main_status: ManageContainerOverride,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +33,7 @@ impl ManageContainer {
|
|||||||
let mut db = seed.ctx.db.handle();
|
let mut db = seed.ctx.db.handle();
|
||||||
let current_state = Arc::new(watch::channel(StartStop::Stop).0);
|
let current_state = Arc::new(watch::channel(StartStop::Stop).0);
|
||||||
let desired_state = Arc::new(
|
let desired_state = Arc::new(
|
||||||
watch::channel::<StartStop>(get_status(&mut db, &seed.manifest).await?.into()).0,
|
watch::channel::<StartStop>(get_status(&mut db, &seed.manifest).await.into()).0,
|
||||||
);
|
);
|
||||||
let override_main_status: ManageContainerOverride = Arc::new(watch::channel(None).0);
|
let override_main_status: ManageContainerOverride = Arc::new(watch::channel(None).0);
|
||||||
let service = tokio::spawn(create_service_manager(
|
let service = tokio::spawn(create_service_manager(
|
||||||
@@ -50,17 +53,19 @@ impl ManageContainer {
|
|||||||
Ok(ManageContainer {
|
Ok(ManageContainer {
|
||||||
current_state,
|
current_state,
|
||||||
desired_state,
|
desired_state,
|
||||||
service,
|
_service: service,
|
||||||
override_main_status,
|
override_main_status,
|
||||||
save_state,
|
_save_state: save_state,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_override(&self, override_status: Option<MainStatus>) -> GeneralBoxedGuard {
|
pub fn set_override(&self, override_status: Option<MainStatus>) -> GeneralBoxedGuard {
|
||||||
self.override_main_status.send(override_status);
|
self.override_main_status
|
||||||
|
.send(override_status)
|
||||||
|
.unwrap_or_default();
|
||||||
let override_main_status = self.override_main_status.clone();
|
let override_main_status = self.override_main_status.clone();
|
||||||
let guard = GeneralBoxedGuard::new(move || {
|
let guard = GeneralBoxedGuard::new(move || {
|
||||||
override_main_status.send(None);
|
override_main_status.send(None).unwrap_or_default();
|
||||||
});
|
});
|
||||||
guard
|
guard
|
||||||
}
|
}
|
||||||
@@ -70,9 +75,9 @@ impl ManageContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_for_desired(&self, new_state: StartStop) {
|
pub async fn wait_for_desired(&self, new_state: StartStop) {
|
||||||
let mut current_state = self.current_state();
|
let current_state = self.current_state();
|
||||||
self.to_desired(new_state);
|
self.to_desired(new_state);
|
||||||
while *current_state.borrow() != new_state {
|
while current_state.borrow() != new_state {
|
||||||
current_state.changed().await.unwrap_or_default();
|
current_state.changed().await.unwrap_or_default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,14 +104,14 @@ async fn create_service_manager(
|
|||||||
let current: StartStop = current_state.borrow().clone();
|
let current: StartStop = current_state.borrow().clone();
|
||||||
let desired: StartStop = desired_state_receiver.borrow().clone();
|
let desired: StartStop = desired_state_receiver.borrow().clone();
|
||||||
match (current, desired) {
|
match (current, desired) {
|
||||||
(StartStop::Start, StartStop::Start) => continue,
|
(StartStop::Start, StartStop::Start) => (),
|
||||||
(StartStop::Start, StartStop::Stop) => {
|
(StartStop::Start, StartStop::Stop) => {
|
||||||
if let Err(err) = seed.stop_container().await {
|
if let Err(err) = seed.stop_container().await {
|
||||||
tracing::error!("Could not stop container");
|
tracing::error!("Could not stop container");
|
||||||
tracing::debug!("{:?}", err)
|
tracing::debug!("{:?}", err)
|
||||||
};
|
};
|
||||||
running_service = None;
|
running_service = None;
|
||||||
current_state.send(StartStop::Stop);
|
current_state.send(StartStop::Stop).unwrap_or_default();
|
||||||
}
|
}
|
||||||
(StartStop::Stop, StartStop::Start) => starting_service(
|
(StartStop::Stop, StartStop::Start) => starting_service(
|
||||||
current_state.clone(),
|
current_state.clone(),
|
||||||
@@ -115,8 +120,9 @@ async fn create_service_manager(
|
|||||||
persistent_container.clone(),
|
persistent_container.clone(),
|
||||||
&mut running_service,
|
&mut running_service,
|
||||||
),
|
),
|
||||||
(StartStop::Stop, StartStop::Stop) => continue,
|
(StartStop::Stop, StartStop::Stop) => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(_) = desired_state_receiver.changed().await {
|
if let Err(_) = desired_state_receiver.changed().await {
|
||||||
tracing::error!("Desired state error");
|
tracing::error!("Desired state error");
|
||||||
break;
|
break;
|
||||||
@@ -188,7 +194,7 @@ fn starting_service(
|
|||||||
let set_running = {
|
let set_running = {
|
||||||
let current_state = current_state.clone();
|
let current_state = current_state.clone();
|
||||||
Arc::new(move || {
|
Arc::new(move || {
|
||||||
current_state.send(StartStop::Start);
|
current_state.send(StartStop::Start).unwrap_or_default();
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let set_stopped = {
|
let set_stopped = {
|
||||||
@@ -203,8 +209,8 @@ fn starting_service(
|
|||||||
set_running.clone(),
|
set_running.clone(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
run_main_log_result(result, seed.clone());
|
set_stopped().unwrap_or_default();
|
||||||
set_stopped();
|
run_main_log_result(result, seed.clone()).await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
*running_service = Some(tokio::spawn(running_main_loop).into());
|
*running_service = Some(tokio::spawn(running_main_loop).into());
|
||||||
@@ -217,24 +223,23 @@ async fn run_main_log_result(result: RunMainResult, seed: Arc<manager_seed::Mana
|
|||||||
#[cfg(feature = "unstable")]
|
#[cfg(feature = "unstable")]
|
||||||
{
|
{
|
||||||
use crate::notifications::NotificationLevel;
|
use crate::notifications::NotificationLevel;
|
||||||
use crate::status::MainStatus;
|
|
||||||
let mut db = seed.ctx.db.handle();
|
let mut db = seed.ctx.db.handle();
|
||||||
let started = crate::db::DatabaseModel::new()
|
let started = crate::db::DatabaseModel::new()
|
||||||
.package_data()
|
.package_data()
|
||||||
.idx_model(&thread_shared.seed.manifest.id)
|
.idx_model(&seed.manifest.id)
|
||||||
.and_then(|pde| pde.installed())
|
.and_then(|pde| pde.installed())
|
||||||
.map::<_, MainStatus>(|i| i.status().main())
|
.map::<_, MainStatus>(|i| i.status().main())
|
||||||
.get(db, false)
|
.get(&mut db)
|
||||||
.await;
|
.await;
|
||||||
match started.as_deref() {
|
match started.as_deref() {
|
||||||
Ok(Some(MainStatus::Running { .. })) => {
|
Ok(Some(MainStatus::Running { .. })) => {
|
||||||
let res = thread_shared.seed.ctx.notification_manager
|
let res = seed.ctx.notification_manager
|
||||||
.notify(
|
.notify(
|
||||||
db,
|
&mut db,
|
||||||
Some(thread_shared.seed.manifest.id.clone()),
|
Some(seed.manifest.id.clone()),
|
||||||
NotificationLevel::Warning,
|
NotificationLevel::Warning,
|
||||||
String::from("Service Crashed"),
|
String::from("Service Crashed"),
|
||||||
format!("The service {} has crashed with the following exit code: {}\nDetails: {}", thread_shared.seed.manifest.id.clone(), e.0, e.1),
|
format!("The service {} has crashed with the following exit code: {}\nDetails: {}", seed.manifest.id.clone(), e.0, e.1),
|
||||||
(),
|
(),
|
||||||
Some(3600) // 1 hour
|
Some(3600) // 1 hour
|
||||||
)
|
)
|
||||||
@@ -249,6 +254,11 @@ async fn run_main_log_result(result: RunMainResult, seed: Arc<manager_seed::Mana
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tracing::error!(
|
||||||
|
"The service {} has crashed with the following exit code: {}",
|
||||||
|
seed.manifest.id.clone(),
|
||||||
|
e.0
|
||||||
|
);
|
||||||
|
|
||||||
tokio::time::sleep(Duration::from_secs(15)).await;
|
tokio::time::sleep(Duration::from_secs(15)).await;
|
||||||
}
|
}
|
||||||
@@ -259,41 +269,56 @@ async fn run_main_log_result(result: RunMainResult, seed: Arc<manager_seed::Mana
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn get_status(
|
#[instrument(skip(db, manifest))]
|
||||||
db: &mut PatchDbHandle,
|
pub(super) async fn get_status(db: &mut PatchDbHandle, manifest: &Manifest) -> MainStatus {
|
||||||
manifest: &Manifest,
|
async move {
|
||||||
) -> Result<MainStatus, Error> {
|
Ok::<_, Error>(
|
||||||
Ok(crate::db::DatabaseModel::new()
|
crate::db::DatabaseModel::new()
|
||||||
.package_data()
|
.package_data()
|
||||||
.idx_model(&manifest.id)
|
.idx_model(&manifest.id)
|
||||||
.expect(db)
|
.expect(db)
|
||||||
.await?
|
.await?
|
||||||
.installed()
|
.installed()
|
||||||
.expect(db)
|
.expect(db)
|
||||||
.await?
|
.await?
|
||||||
.status()
|
.status()
|
||||||
.main()
|
.main()
|
||||||
.get(db)
|
.get(db)
|
||||||
.await?
|
.await?
|
||||||
.clone())
|
.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.map(|x| x.unwrap_or_else(|e| MainStatus::Stopped))
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(db, manifest))]
|
||||||
async fn set_status(
|
async fn set_status(
|
||||||
db: &mut PatchDbHandle,
|
db: &mut PatchDbHandle,
|
||||||
manifest: &Manifest,
|
manifest: &Manifest,
|
||||||
main_status: &MainStatus,
|
main_status: &MainStatus,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
crate::db::DatabaseModel::new()
|
if crate::db::DatabaseModel::new()
|
||||||
.package_data()
|
.package_data()
|
||||||
.idx_model(&manifest.id)
|
.idx_model(&manifest.id)
|
||||||
.expect(db)
|
.expect(db)
|
||||||
.await?
|
.await?
|
||||||
.installed()
|
.installed()
|
||||||
.expect(db)
|
.exists(db)
|
||||||
.await?
|
.await?
|
||||||
.status()
|
{
|
||||||
.main()
|
crate::db::DatabaseModel::new()
|
||||||
.put(db, main_status)
|
.package_data()
|
||||||
.await?;
|
.idx_model(&manifest.id)
|
||||||
|
.expect(db)
|
||||||
|
.await?
|
||||||
|
.installed()
|
||||||
|
.expect(db)
|
||||||
|
.await?
|
||||||
|
.status()
|
||||||
|
.main()
|
||||||
|
.put(db, main_status)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use std::time::Duration;
|
|||||||
use color_eyre::{eyre::eyre, Report};
|
use color_eyre::{eyre::eyre, Report};
|
||||||
use embassy_container_init::ProcessGroupId;
|
use embassy_container_init::ProcessGroupId;
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use futures::{FutureExt, TryFutureExt};
|
use futures::{Future, FutureExt, TryFutureExt};
|
||||||
use helpers::UnixRpcClient;
|
use helpers::UnixRpcClient;
|
||||||
use models::{ErrorKind, PackageId};
|
use models::{ErrorKind, PackageId};
|
||||||
use nix::sys::signal::Signal;
|
use nix::sys::signal::Signal;
|
||||||
@@ -168,19 +168,22 @@ impl Manager {
|
|||||||
self._transition_replace({
|
self._transition_replace({
|
||||||
let manage_container = self.manage_container.clone();
|
let manage_container = self.manage_container.clone();
|
||||||
|
|
||||||
TransitionState::Configuring(tokio::spawn(async move {
|
TransitionState::Configuring(
|
||||||
let desired_state = manage_container.desired_state();
|
tokio::spawn(async move {
|
||||||
let state_reverter = DesiredStateReverter::new(manage_container.clone());
|
let desired_state = manage_container.desired_state();
|
||||||
let mut current_state = manage_container.current_state();
|
let state_reverter = DesiredStateReverter::new(manage_container.clone());
|
||||||
manage_container.to_desired(StartStop::Stop);
|
let mut current_state = manage_container.current_state();
|
||||||
while current_state.borrow().is_start() {
|
manage_container.to_desired(StartStop::Stop);
|
||||||
current_state.changed().await.unwrap();
|
while current_state.borrow().is_start() {
|
||||||
}
|
current_state.changed().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
transition_state.await;
|
transition_state.await;
|
||||||
|
|
||||||
state_reverter.revert().await;
|
state_reverter.revert().await;
|
||||||
}))
|
})
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
done.await
|
done.await
|
||||||
}
|
}
|
||||||
@@ -230,69 +233,76 @@ impl Manager {
|
|||||||
.send_replace(Arc::new(transition_state))
|
.send_replace(Arc::new(transition_state))
|
||||||
.abort();
|
.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn perform_restart(&self) -> impl Future<Output = ()> + 'static {
|
||||||
|
let manage_container = self.manage_container.clone();
|
||||||
|
async move {
|
||||||
|
let _ = manage_container.set_override(Some(MainStatus::Restarting));
|
||||||
|
manage_container.wait_for_desired(StartStop::Stop).await;
|
||||||
|
manage_container.wait_for_desired(StartStop::Start).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
fn _transition_restart(&self) -> TransitionState {
|
fn _transition_restart(&self) -> TransitionState {
|
||||||
let transition = self.transition.clone();
|
let transition = self.transition.clone();
|
||||||
|
let restart = self.perform_restart();
|
||||||
|
TransitionState::Restarting(
|
||||||
|
tokio::spawn(async move {
|
||||||
|
restart.await;
|
||||||
|
transition.send_replace(Default::default());
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn perform_backup(
|
||||||
|
&self,
|
||||||
|
backup_guard: BackupGuard,
|
||||||
|
) -> impl Future<Output = Result<Result<PackageBackupInfo, Error>, Error>> + 'static {
|
||||||
let manage_container = self.manage_container.clone();
|
let manage_container = self.manage_container.clone();
|
||||||
TransitionState::Restarting(tokio::spawn(async move {
|
let seed = self.seed.clone();
|
||||||
let mut current_state = manage_container.current_state();
|
async move {
|
||||||
let _ = manage_container.set_override(Some(MainStatus::Restarting));
|
let state_reverter = DesiredStateReverter::new(manage_container.clone());
|
||||||
manage_container.to_desired(StartStop::Stop);
|
let mut tx = seed.ctx.db.handle();
|
||||||
while current_state.borrow().is_start() {
|
let _ = manage_container
|
||||||
current_state.changed().await.unwrap();
|
.set_override(Some(get_status(&mut tx, &seed.manifest).await.backing_up()));
|
||||||
}
|
manage_container.wait_for_desired(StartStop::Stop).await;
|
||||||
manage_container.to_desired(StartStop::Start);
|
|
||||||
while current_state.borrow().is_stop() {
|
let backup_guard = backup_guard.lock().await;
|
||||||
current_state.changed().await.unwrap();
|
let guard = backup_guard.mount_package_backup(&seed.manifest.id).await?;
|
||||||
}
|
|
||||||
transition.send_replace(Default::default());
|
let res = seed
|
||||||
}))
|
.manifest
|
||||||
|
.backup
|
||||||
|
.create(
|
||||||
|
&seed.ctx,
|
||||||
|
&mut tx,
|
||||||
|
&seed.manifest.id,
|
||||||
|
&seed.manifest.title,
|
||||||
|
&seed.manifest.version,
|
||||||
|
&seed.manifest.interfaces,
|
||||||
|
&seed.manifest.volumes,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
guard.unmount().await?;
|
||||||
|
drop(backup_guard);
|
||||||
|
|
||||||
|
let return_value = res;
|
||||||
|
state_reverter.revert().await;
|
||||||
|
Ok::<_, Error>(return_value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn _transition_backup(
|
fn _transition_backup(
|
||||||
&self,
|
&self,
|
||||||
backup_guard: BackupGuard,
|
backup_guard: BackupGuard,
|
||||||
) -> (TransitionState, BoxFuture<BackupReturn>) {
|
) -> (TransitionState, BoxFuture<BackupReturn>) {
|
||||||
let manage_container = self.manage_container.clone();
|
|
||||||
let seed = self.seed.clone();
|
|
||||||
let (send, done) = oneshot::channel();
|
let (send, done) = oneshot::channel();
|
||||||
(
|
(
|
||||||
TransitionState::BackingUp(tokio::spawn(
|
TransitionState::BackingUp(
|
||||||
async move {
|
tokio::spawn(
|
||||||
let state_reverter = DesiredStateReverter::new(manage_container.clone());
|
self.perform_backup(backup_guard)
|
||||||
let mut current_state = manage_container.current_state();
|
.then(finnish_up_backup_task(self.transition.clone(), send)),
|
||||||
let mut tx = seed.ctx.db.handle();
|
)
|
||||||
let _ = manage_container.set_override(Some(
|
.into(),
|
||||||
get_status(&mut tx, &seed.manifest).await?.backing_up(),
|
),
|
||||||
));
|
|
||||||
manage_container.to_desired(StartStop::Stop);
|
|
||||||
while current_state.borrow().is_start() {
|
|
||||||
current_state.changed().await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let backup_guard = backup_guard.lock().await;
|
|
||||||
let guard = backup_guard.mount_package_backup(&seed.manifest.id).await?;
|
|
||||||
|
|
||||||
let res = seed
|
|
||||||
.manifest
|
|
||||||
.backup
|
|
||||||
.create(
|
|
||||||
&seed.ctx,
|
|
||||||
&mut tx,
|
|
||||||
&seed.manifest.id,
|
|
||||||
&seed.manifest.title,
|
|
||||||
&seed.manifest.version,
|
|
||||||
&seed.manifest.interfaces,
|
|
||||||
&seed.manifest.volumes,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
guard.unmount().await?;
|
|
||||||
drop(backup_guard);
|
|
||||||
|
|
||||||
let return_value = res;
|
|
||||||
state_reverter.revert().await;
|
|
||||||
Ok::<_, Error>(return_value)
|
|
||||||
}
|
|
||||||
.then(finnish_up_backup_task(self.transition.clone(), send)), //
|
|
||||||
)),
|
|
||||||
done.map_err(|err| Error::new(eyre!("Oneshot error: {err:?}"), ErrorKind::Unknown))
|
done.map_err(|err| Error::new(eyre!("Oneshot error: {err:?}"), ErrorKind::Unknown))
|
||||||
.map(flatten_backup_error)
|
.map(flatten_backup_error)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
@@ -683,11 +693,13 @@ async fn run_main(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let svc = if let Some(ip) = ip {
|
let svc = if let Some(ip) = ip {
|
||||||
Some(add_network_for_main(&*seed, ip).await?)
|
let net = add_network_for_main(&seed, ip).await?;
|
||||||
|
started();
|
||||||
|
Some(net)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
started();
|
|
||||||
let health = main_health_check_daemon(seed.clone());
|
let health = main_health_check_daemon(seed.clone());
|
||||||
let res = tokio::select! {
|
let res = tokio::select! {
|
||||||
a = runtime => a.map_err(|_| Error::new(eyre!("Manager runtime panicked!"), crate::ErrorKind::Docker)).and_then(|a| a),
|
a = runtime => a.map_err(|_| Error::new(eyre!("Manager runtime panicked!"), crate::ErrorKind::Docker)).and_then(|a| a),
|
||||||
@@ -774,6 +786,7 @@ async fn get_long_running_ip(seed: &ManagerSeed, runtime: &mut LongRunning) -> G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(seed))]
|
||||||
async fn container_inspect(
|
async fn container_inspect(
|
||||||
seed: &ManagerSeed,
|
seed: &ManagerSeed,
|
||||||
) -> Result<bollard::models::ContainerInspectResponse, bollard::errors::Error> {
|
) -> Result<bollard::models::ContainerInspectResponse, bollard::errors::Error> {
|
||||||
@@ -783,6 +796,7 @@ async fn container_inspect(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(seed))]
|
||||||
async fn add_network_for_main(
|
async fn add_network_for_main(
|
||||||
seed: &ManagerSeed,
|
seed: &ManagerSeed,
|
||||||
ip: std::net::Ipv4Addr,
|
ip: std::net::Ipv4Addr,
|
||||||
@@ -814,6 +828,7 @@ async fn add_network_for_main(
|
|||||||
Ok(svc)
|
Ok(svc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(svc))]
|
||||||
async fn remove_network_for_main(svc: NetService) -> Result<(), Error> {
|
async fn remove_network_for_main(svc: NetService) -> Result<(), Error> {
|
||||||
svc.remove_all().await
|
svc.remove_all().await
|
||||||
}
|
}
|
||||||
@@ -848,6 +863,7 @@ async fn try_get_running_ip(seed: &ManagerSeed) -> Result<Option<Ipv4Addr>, Repo
|
|||||||
.transpose()?)
|
.transpose()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(seed, runtime))]
|
||||||
async fn get_running_ip(seed: &ManagerSeed, mut runtime: &mut RuntimeOfCommand) -> GetRunningIp {
|
async fn get_running_ip(seed: &ManagerSeed, mut runtime: &mut RuntimeOfCommand) -> GetRunningIp {
|
||||||
loop {
|
loop {
|
||||||
match container_inspect(seed).await {
|
match container_inspect(seed).await {
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
use tokio::task::JoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
|
|
||||||
pub(crate) enum TransitionState {
|
pub(crate) enum TransitionState {
|
||||||
// Starting(JoinHandle<()>),
|
BackingUp(NonDetachingJoinHandle<()>),
|
||||||
// Stopping(JoinHandle<()>)
|
Restarting(NonDetachingJoinHandle<()>),
|
||||||
BackingUp(JoinHandle<()>),
|
Configuring(NonDetachingJoinHandle<()>),
|
||||||
Restarting(JoinHandle<()>),
|
|
||||||
Configuring(JoinHandle<()>),
|
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransitionState {
|
impl TransitionState {
|
||||||
pub(crate) fn join_handle(&self) -> Option<&JoinHandle<()>> {
|
pub(crate) fn join_handle(&self) -> Option<&NonDetachingJoinHandle<()>> {
|
||||||
Some(match self {
|
Some(match self {
|
||||||
TransitionState::BackingUp(a) => a,
|
TransitionState::BackingUp(a) => a,
|
||||||
TransitionState::Restarting(a) => a,
|
TransitionState::Restarting(a) => a,
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ use p256::elliptic_curve::pkcs8::EncodePrivateKey;
|
|||||||
use sqlx::PgExecutor;
|
use sqlx::PgExecutor;
|
||||||
use ssh_key::private::Ed25519PrivateKey;
|
use ssh_key::private::Ed25519PrivateKey;
|
||||||
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
use torut::onion::{OnionAddressV3, TorSecretKeyV3};
|
||||||
|
use tracing::instrument;
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use crate::net::ssl::CertPair;
|
use crate::net::ssl::CertPair;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
// TODO: delete once we may change tor addresses
|
// TODO: delete once we may change tor addresses
|
||||||
|
#[instrument(skip(secrets))]
|
||||||
async fn compat(
|
async fn compat(
|
||||||
secrets: impl PgExecutor<'_>,
|
secrets: impl PgExecutor<'_>,
|
||||||
interface: &Option<(PackageId, InterfaceId)>,
|
interface: &Option<(PackageId, InterfaceId)>,
|
||||||
@@ -182,6 +184,7 @@ impl Key {
|
|||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
#[instrument(skip(secrets))]
|
||||||
pub async fn for_interface<Ex>(
|
pub async fn for_interface<Ex>(
|
||||||
secrets: &mut Ex,
|
secrets: &mut Ex,
|
||||||
interface: Option<(PackageId, InterfaceId)>,
|
interface: Option<(PackageId, InterfaceId)>,
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ pub struct NetService {
|
|||||||
lan: BTreeMap<(InterfaceId, u16), (Key, Vec<Arc<()>>)>,
|
lan: BTreeMap<(InterfaceId, u16), (Key, Vec<Arc<()>>)>,
|
||||||
}
|
}
|
||||||
impl NetService {
|
impl NetService {
|
||||||
|
#[instrument(skip(self))]
|
||||||
fn net_controller(&self) -> Result<Arc<NetController>, Error> {
|
fn net_controller(&self) -> Result<Arc<NetController>, Error> {
|
||||||
Weak::upgrade(&self.controller).ok_or_else(|| {
|
Weak::upgrade(&self.controller).ok_or_else(|| {
|
||||||
Error::new(
|
Error::new(
|
||||||
@@ -223,6 +224,7 @@ impl NetService {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
#[instrument(skip(self, secrets))]
|
||||||
pub async fn add_tor<Ex>(
|
pub async fn add_tor<Ex>(
|
||||||
&mut self,
|
&mut self,
|
||||||
secrets: &mut Ex,
|
secrets: &mut Ex,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use clap::ArgMatches;
|
|||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use sqlx::{Executor, Pool, Postgres};
|
use sqlx::{Executor, Pool, Postgres};
|
||||||
use ssh_key::private::Ed25519PrivateKey;
|
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export async function getConfig(effects) {
|
|||||||
volumeId: "main",
|
volumeId: "main",
|
||||||
});
|
});
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Expecting that the ../test.log should not be a valid path since we are breaking out of the parent"
|
"Expecting that the ../test.log should not be a valid path since we are breaking out of the parent",
|
||||||
);
|
);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
try {
|
try {
|
||||||
@@ -20,7 +20,7 @@ export async function getConfig(effects) {
|
|||||||
volumeId: "main",
|
volumeId: "main",
|
||||||
});
|
});
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Expecting that using a symlink to break out of parent still fails for writing"
|
"Expecting that using a symlink to break out of parent still fails for writing",
|
||||||
);
|
);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
try {
|
try {
|
||||||
@@ -29,7 +29,7 @@ export async function getConfig(effects) {
|
|||||||
volumeId: "main",
|
volumeId: "main",
|
||||||
});
|
});
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Expecting that using a symlink to break out of parent still fails for writing dir"
|
"Expecting that using a symlink to break out of parent still fails for writing dir",
|
||||||
);
|
);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
try {
|
try {
|
||||||
@@ -38,7 +38,7 @@ export async function getConfig(effects) {
|
|||||||
volumeId: "main",
|
volumeId: "main",
|
||||||
});
|
});
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Expecting that using a symlink to break out of parent still fails for reading"
|
"Expecting that using a symlink to break out of parent still fails for reading",
|
||||||
);
|
);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ export async function getConfig(effects) {
|
|||||||
`Read results are ${await effects.readFile({
|
`Read results are ${await effects.readFile({
|
||||||
path: "./test.log",
|
path: "./test.log",
|
||||||
volumeId: "main",
|
volumeId: "main",
|
||||||
})}`
|
})}`,
|
||||||
);
|
);
|
||||||
// Testing loging
|
// Testing loging
|
||||||
effects.trace("trace");
|
effects.trace("trace");
|
||||||
@@ -730,48 +730,47 @@ export async function setConfig(effects) {
|
|||||||
|
|
||||||
const assert = (condition, message) => {
|
const assert = (condition, message) => {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
throw ({error: message});
|
throw ({ error: message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const ackermann = (m, n) => {
|
const ackermann = (m, n) => {
|
||||||
if (m === 0) {
|
if (m === 0) {
|
||||||
return n+1
|
return n + 1;
|
||||||
}
|
}
|
||||||
if (n === 0) {
|
if (n === 0) {
|
||||||
return ackermann((m - 1), 1);
|
return ackermann(m - 1, 1);
|
||||||
}
|
}
|
||||||
if (m !== 0 && n !== 0) {
|
if (m !== 0 && n !== 0) {
|
||||||
return ackermann((m-1), ackermann(m, (n-1)))
|
return ackermann(m - 1, ackermann(m, n - 1));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const action = {
|
export const action = {
|
||||||
async slow(effects, _input) {
|
async slow(effects, _input) {
|
||||||
while(true) {
|
while (true) {
|
||||||
effects.error("A");
|
effects.error("A");
|
||||||
await ackermann(3,10);
|
await ackermann(3, 10);
|
||||||
// await effects.sleep(100);
|
// await effects.sleep(100);
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetch(effects, _input) {
|
async fetch(effects, _input) {
|
||||||
const example = await effects.fetch(
|
const example = await effects.fetch(
|
||||||
"https://postman-echo.com/get?foo1=bar1&foo2=bar2"
|
"https://postman-echo.com/get?foo1=bar1&foo2=bar2",
|
||||||
);
|
);
|
||||||
assert(
|
assert(
|
||||||
Number(example.headers["content-length"]) > 0 &&
|
Number(example.headers["content-length"]) > 0 &&
|
||||||
Number(example.headers["content-length"]) <= 1000000,
|
Number(example.headers["content-length"]) <= 1000000,
|
||||||
"Should have content length"
|
"Should have content length",
|
||||||
);
|
);
|
||||||
assert(
|
assert(
|
||||||
example.text() instanceof Promise,
|
example.text() instanceof Promise,
|
||||||
"example.text() should be a promise"
|
"example.text() should be a promise",
|
||||||
);
|
);
|
||||||
assert(example.body === undefined, "example.body should not be defined");
|
assert(example.body === undefined, "example.body should not be defined");
|
||||||
assert(
|
assert(
|
||||||
JSON.parse(await example.text()).args.foo1 === "bar1",
|
JSON.parse(await example.text()).args.foo1 === "bar1",
|
||||||
"Body should be parsed"
|
"Body should be parsed",
|
||||||
);
|
);
|
||||||
const message = `This worked @ ${new Date().toISOString()}`;
|
const message = `This worked @ ${new Date().toISOString()}`;
|
||||||
const secondResponse = await effects.fetch(
|
const secondResponse = await effects.fetch(
|
||||||
@@ -782,11 +781,11 @@ export const action = {
|
|||||||
headers: {
|
headers: {
|
||||||
test: "1234",
|
test: "1234",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
assert(
|
assert(
|
||||||
(await secondResponse.json()).json.message === message,
|
(await secondResponse.json()).json.message === message,
|
||||||
"Body should be parsed from response"
|
"Body should be parsed from response",
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
result: {
|
result: {
|
||||||
@@ -845,7 +844,6 @@ export const action = {
|
|||||||
}
|
}
|
||||||
assert(failed, "Should not be able to remove file that doesn't exist");
|
assert(failed, "Should not be able to remove file that doesn't exist");
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: {
|
result: {
|
||||||
copyable: false,
|
copyable: false,
|
||||||
@@ -969,7 +967,9 @@ export const action = {
|
|||||||
await effects.createDir({
|
await effects.createDir({
|
||||||
volumeId: "main",
|
volumeId: "main",
|
||||||
path: "test-deep-dir/../../test",
|
path: "test-deep-dir/../../test",
|
||||||
}).then(_ => {throw new Error("Should not be able to create sub")}, _ => {});
|
}).then((_) => {
|
||||||
|
throw new Error("Should not be able to create sub");
|
||||||
|
}, (_) => {});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: {
|
result: {
|
||||||
@@ -981,7 +981,6 @@ export const action = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Want to test that rsync works
|
* Want to test that rsync works
|
||||||
* @param {*} effects
|
* @param {*} effects
|
||||||
@@ -1005,17 +1004,22 @@ export const action = {
|
|||||||
delete: true,
|
delete: true,
|
||||||
force: true,
|
force: true,
|
||||||
ignoreExisting: false,
|
ignoreExisting: false,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
assert(await runningRsync.id() >= 1, "Expect that we have an id");
|
assert(await runningRsync.id() >= 1, "Expect that we have an id");
|
||||||
const progress = await runningRsync.progress()
|
const progress = await runningRsync.progress();
|
||||||
assert(progress >= 0 && progress <= 1, `Expect progress to be 0 <= progress(${progress}) <= 1`);
|
assert(
|
||||||
|
progress >= 0 && progress <= 1,
|
||||||
|
`Expect progress to be 0 <= progress(${progress}) <= 1`,
|
||||||
|
);
|
||||||
await runningRsync.wait();
|
await runningRsync.wait();
|
||||||
assert((await effects.readFile({
|
assert(
|
||||||
volumeId: "main",
|
(await effects.readFile({
|
||||||
path: "test-rsync-out/testing-rsync/someFile.txt",
|
volumeId: "main",
|
||||||
})).length > 0, 'Asserting that we read in the file "test_rsync/test-package/0.3.0.3/embassy.js"');
|
path: "test-rsync-out/testing-rsync/someFile.txt",
|
||||||
|
})).length > 0,
|
||||||
|
'Asserting that we read in the file "test_rsync/test-package/0.3.0.3/embassy.js"',
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: {
|
result: {
|
||||||
@@ -1025,11 +1029,9 @@ export const action = {
|
|||||||
qr: false,
|
qr: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
} catch (e) {
|
||||||
catch (e) {
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
} finally {
|
||||||
finally {
|
|
||||||
await effects
|
await effects
|
||||||
.removeDir({
|
.removeDir({
|
||||||
volumeId: "main",
|
volumeId: "main",
|
||||||
@@ -1046,12 +1048,18 @@ export const action = {
|
|||||||
*/
|
*/
|
||||||
async "test-callback"(effects, _input) {
|
async "test-callback"(effects, _input) {
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
new Promise(done => effects.getServiceConfig({serviceId: 'something', configPath: "string", onChange: done})),
|
new Promise((done) =>
|
||||||
new Promise (async () => {
|
effects.getServiceConfig({
|
||||||
await effects.sleep(100)
|
serviceId: "something",
|
||||||
throw new Error("Currently in sleeping")
|
configPath: "string",
|
||||||
}
|
onChange: done,
|
||||||
)])
|
})
|
||||||
|
),
|
||||||
|
new Promise(async () => {
|
||||||
|
await effects.sleep(100);
|
||||||
|
throw new Error("Currently in sleeping");
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: {
|
result: {
|
||||||
@@ -1090,30 +1098,39 @@ export const action = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const firstMetaData = await effects.metadata({
|
const firstMetaData = await effects.metadata({
|
||||||
volumeId: 'main',
|
volumeId: "main",
|
||||||
path: 'pem-chown/deep/123/test.txt',
|
path: "pem-chown/deep/123/test.txt",
|
||||||
})
|
});
|
||||||
assert(firstMetaData.readonly === false, `The readonly (${firstMetaData.readonly}) is wrong`);
|
assert(
|
||||||
|
firstMetaData.readonly === false,
|
||||||
|
`The readonly (${firstMetaData.readonly}) is wrong`,
|
||||||
|
);
|
||||||
const previousUid = firstMetaData.uid;
|
const previousUid = firstMetaData.uid;
|
||||||
const expected = 1234
|
const expected = 1234;
|
||||||
|
|
||||||
await effects.setPermissions({
|
await effects.chmod({
|
||||||
volumeId: 'main',
|
volumeId: "main",
|
||||||
path: 'pem-chown/deep/123/test.txt',
|
path: "pem-chown/deep/123/test.txt",
|
||||||
readonly: true
|
mode: 0o444,
|
||||||
})
|
});
|
||||||
const chownError = await effects.chown({
|
const chownError = await effects.chown({
|
||||||
volumeId: 'main',
|
volumeId: "main",
|
||||||
path: 'pem-chown/deep',
|
path: "pem-chown/deep",
|
||||||
uid: expected
|
uid: expected,
|
||||||
}).then(() => true, () => false)
|
}).then(() => true, () => false);
|
||||||
let metaData = await effects.metadata({
|
let metaData = await effects.metadata({
|
||||||
volumeId: 'main',
|
volumeId: "main",
|
||||||
path: 'pem-chown/deep/123/test.txt',
|
path: "pem-chown/deep/123/test.txt",
|
||||||
})
|
});
|
||||||
assert(metaData.readonly === true, `The readonly (${metaData.readonly}) is wrong`);
|
|
||||||
if (chownError) {
|
if (chownError) {
|
||||||
assert(metaData.uid === expected, `The uuid (${metaData.uid}) is wrong, should be more than ${previousUid}`);
|
assert(
|
||||||
|
metaData.mode === 0o444,
|
||||||
|
`The mode (${metaData.mode}) is wrong compared to ${0o444}}`,
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
metaData.uid === expected,
|
||||||
|
`The uuid (${metaData.uid}) is wrong, should be more than ${previousUid}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1132,4 +1149,3 @@ export const action = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -93,6 +94,17 @@ impl<T> Future for NonDetachingJoinHandle<T> {
|
|||||||
this.0.poll(cx)
|
this.0.poll(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<T> Deref for NonDetachingJoinHandle<T> {
|
||||||
|
type Target = JoinHandle<T>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> DerefMut for NonDetachingJoinHandle<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AtomicFile {
|
pub struct AtomicFile {
|
||||||
tmp_path: PathBuf,
|
tmp_path: PathBuf,
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ pub trait OsApi: Send + Sync + 'static {
|
|||||||
|
|
||||||
async fn unbind_local(&self, id: InterfaceId, external: u16) -> Result<(), Report>;
|
async fn unbind_local(&self, id: InterfaceId, external: u16) -> Result<(), Report>;
|
||||||
async fn unbind_onion(&self, id: InterfaceId, external: u16) -> Result<(), Report>;
|
async fn unbind_onion(&self, id: InterfaceId, external: u16) -> Result<(), Report>;
|
||||||
fn set_started(&self) -> Result<(), Report>;
|
fn set_started(&self);
|
||||||
async fn restart(&self) -> Result<(), Report>;
|
async fn restart(&self);
|
||||||
async fn start(&self) -> Result<(), Report>;
|
async fn start(&self);
|
||||||
async fn stop(&self) -> Result<(), Report>;
|
async fn stop(&self);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import Deno from "/deno_global.js";
|
import Deno from "/deno_global.js";
|
||||||
import * as mainModule from "/embassy.js";
|
import * as mainModule from "/embassy.js";
|
||||||
|
|
||||||
// throw new Error("I'm going crasy")
|
|
||||||
|
|
||||||
function requireParam(param) {
|
function requireParam(param) {
|
||||||
throw new Error(`Missing required parameter ${param}`);
|
throw new Error(`Missing required parameter ${param}`);
|
||||||
}
|
}
|
||||||
@@ -49,10 +47,8 @@ const writeFile = (
|
|||||||
) => Deno.core.opAsync("write_file", volumeId, path, toWrite);
|
) => Deno.core.opAsync("write_file", volumeId, path, toWrite);
|
||||||
|
|
||||||
const readFile = (
|
const readFile = (
|
||||||
{
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } =
|
||||||
volumeId = requireParam("volumeId"),
|
requireParam("options"),
|
||||||
path = requireParam("path"),
|
|
||||||
} = requireParam("options"),
|
|
||||||
) => Deno.core.opAsync("read_file", volumeId, path);
|
) => Deno.core.opAsync("read_file", volumeId, path);
|
||||||
|
|
||||||
const runDaemon = (
|
const runDaemon = (
|
||||||
@@ -74,11 +70,8 @@ const runDaemon = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
const runCommand = async (
|
const runCommand = async (
|
||||||
{
|
{ command = requireParam("command"), args = [], timeoutMillis = 30000 } =
|
||||||
command = requireParam("command"),
|
requireParam("options"),
|
||||||
args = [],
|
|
||||||
timeoutMillis = 30000,
|
|
||||||
} = requireParam("options"),
|
|
||||||
) => {
|
) => {
|
||||||
let id = Deno.core.opAsync(
|
let id = Deno.core.opAsync(
|
||||||
"start_command",
|
"start_command",
|
||||||
@@ -128,10 +121,8 @@ const rename = (
|
|||||||
} = requireParam("options"),
|
} = requireParam("options"),
|
||||||
) => Deno.core.opAsync("rename", srcVolume, srcPath, dstVolume, dstPath);
|
) => Deno.core.opAsync("rename", srcVolume, srcPath, dstVolume, dstPath);
|
||||||
const metadata = async (
|
const metadata = async (
|
||||||
{
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } =
|
||||||
volumeId = requireParam("volumeId"),
|
requireParam("options"),
|
||||||
path = requireParam("path"),
|
|
||||||
} = requireParam("options"),
|
|
||||||
) => {
|
) => {
|
||||||
const data = await Deno.core.opAsync("metadata", volumeId, path);
|
const data = await Deno.core.opAsync("metadata", volumeId, path);
|
||||||
return {
|
return {
|
||||||
@@ -142,10 +133,8 @@ const metadata = async (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
const removeFile = (
|
const removeFile = (
|
||||||
{
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } =
|
||||||
volumeId = requireParam("volumeId"),
|
requireParam("options"),
|
||||||
path = requireParam("path"),
|
|
||||||
} = requireParam("options"),
|
|
||||||
) => Deno.core.opAsync("remove_file", volumeId, path);
|
) => Deno.core.opAsync("remove_file", volumeId, path);
|
||||||
const isSandboxed = () => Deno.core.opSync("is_sandboxed");
|
const isSandboxed = () => Deno.core.opSync("is_sandboxed");
|
||||||
|
|
||||||
@@ -182,26 +171,20 @@ const chmod = async (
|
|||||||
return await Deno.core.opAsync("chmod", volumeId, path, mode);
|
return await Deno.core.opAsync("chmod", volumeId, path, mode);
|
||||||
};
|
};
|
||||||
const readJsonFile = async (
|
const readJsonFile = async (
|
||||||
{
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } =
|
||||||
volumeId = requireParam("volumeId"),
|
requireParam("options"),
|
||||||
path = requireParam("path"),
|
|
||||||
} = requireParam("options"),
|
|
||||||
) => JSON.parse(await readFile({ volumeId, path }));
|
) => JSON.parse(await readFile({ volumeId, path }));
|
||||||
const createDir = (
|
const createDir = (
|
||||||
{
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } =
|
||||||
volumeId = requireParam("volumeId"),
|
requireParam("options"),
|
||||||
path = requireParam("path"),
|
|
||||||
} = requireParam("options"),
|
|
||||||
) => Deno.core.opAsync("create_dir", volumeId, path);
|
) => Deno.core.opAsync("create_dir", volumeId, path);
|
||||||
|
|
||||||
const readDir = (
|
const readDir = (
|
||||||
{ volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"),
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } = requireParam("options"),
|
||||||
) => Deno.core.opAsync("read_dir", volumeId, path);
|
) => Deno.core.opAsync("read_dir", volumeId, path);
|
||||||
const removeDir = (
|
const removeDir = (
|
||||||
{
|
{ volumeId = requireParam("volumeId"), path = requireParam("path") } =
|
||||||
volumeId = requireParam("volumeId"),
|
requireParam("options"),
|
||||||
path = requireParam("path"),
|
|
||||||
} = requireParam("options"),
|
|
||||||
) => Deno.core.opAsync("remove_dir", volumeId, path);
|
) => Deno.core.opAsync("remove_dir", volumeId, path);
|
||||||
const trace = (whatToTrace = requireParam("whatToTrace")) =>
|
const trace = (whatToTrace = requireParam("whatToTrace")) =>
|
||||||
Deno.core.opAsync("log_trace", whatToTrace);
|
Deno.core.opAsync("log_trace", whatToTrace);
|
||||||
@@ -276,15 +259,10 @@ const getServiceConfig = async (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPermissions = async (
|
const started = () => Deno.core.opSync("set_started");
|
||||||
{
|
const restart = () => Deno.core.opAsync("restart");
|
||||||
volumeId = requireParam("volumeId"),
|
const start = () => Deno.core.opAsync("start");
|
||||||
path = requireParam("path"),
|
const stop = () => Deno.core.opAsync("stop");
|
||||||
readonly = requireParam("readonly"),
|
|
||||||
} = requireParam("options"),
|
|
||||||
) => {
|
|
||||||
return await Deno.core.opAsync("set_permissions", volumeId, path, readonly);
|
|
||||||
};
|
|
||||||
|
|
||||||
const currentFunction = Deno.core.opSync("current_function");
|
const currentFunction = Deno.core.opSync("current_function");
|
||||||
const input = Deno.core.opSync("get_input");
|
const input = Deno.core.opSync("get_input");
|
||||||
@@ -314,13 +292,19 @@ const effects = {
|
|||||||
runCommand,
|
runCommand,
|
||||||
runDaemon,
|
runDaemon,
|
||||||
runRsync,
|
runRsync,
|
||||||
setPermissions,
|
chmod,
|
||||||
signalGroup,
|
signalGroup,
|
||||||
sleep,
|
sleep,
|
||||||
trace,
|
trace,
|
||||||
warn,
|
warn,
|
||||||
writeFile,
|
writeFile,
|
||||||
writeJsonFile,
|
writeJsonFile,
|
||||||
|
restart,
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
};
|
||||||
|
const fnSpecificArgs = {
|
||||||
|
main: { started },
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
@@ -337,8 +321,10 @@ function safeToString(fn, orValue = "") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const apiVersion = mainModule?.version || defaults?.version || 0;
|
||||||
const runFunction = jsonPointerValue(mainModule, currentFunction) ||
|
const runFunction = jsonPointerValue(mainModule, currentFunction) ||
|
||||||
jsonPointerValue(defaults, currentFunction);
|
jsonPointerValue(defaults, currentFunction);
|
||||||
|
const extraArgs = jsonPointerValue(fnSpecificArgs, currentFunction) || {};
|
||||||
(async () => {
|
(async () => {
|
||||||
const answer = await (async () => {
|
const answer = await (async () => {
|
||||||
if (typeof runFunction !== "function") {
|
if (typeof runFunction !== "function") {
|
||||||
@@ -346,7 +332,21 @@ const runFunction = jsonPointerValue(mainModule, currentFunction) ||
|
|||||||
throw new Error(`Expecting ${currentFunction} to be a function`);
|
throw new Error(`Expecting ${currentFunction} to be a function`);
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
.then(() => runFunction(effects, input, ...variable_args))
|
.then(() => {
|
||||||
|
switch (apiVersion) {
|
||||||
|
case 0:
|
||||||
|
return runFunction(effects, input, ...variable_args);
|
||||||
|
case 1:
|
||||||
|
return runFunction({
|
||||||
|
effects,
|
||||||
|
input,
|
||||||
|
args: variable_args,
|
||||||
|
...extraArgs,
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return { error: `Unknown API version ${apiVersion}` };
|
||||||
|
}
|
||||||
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
if ("error" in e) return e;
|
if ("error" in e) return e;
|
||||||
if ("error-code" in e) return e;
|
if ("error-code" in e) return e;
|
||||||
|
|||||||
@@ -317,12 +317,16 @@ impl JsExecutionEnvironment {
|
|||||||
fns::wait_command::decl(),
|
fns::wait_command::decl(),
|
||||||
fns::sleep::decl(),
|
fns::sleep::decl(),
|
||||||
fns::send_signal::decl(),
|
fns::send_signal::decl(),
|
||||||
fns::set_permissions::decl(),
|
fns::chmod::decl(),
|
||||||
fns::signal_group::decl(),
|
fns::signal_group::decl(),
|
||||||
fns::rsync::decl(),
|
fns::rsync::decl(),
|
||||||
fns::rsync_wait::decl(),
|
fns::rsync_wait::decl(),
|
||||||
fns::rsync_progress::decl(),
|
fns::rsync_progress::decl(),
|
||||||
fns::get_service_config::decl(),
|
fns::get_service_config::decl(),
|
||||||
|
fns::set_started::decl(),
|
||||||
|
fns::restart::decl(),
|
||||||
|
fns::start::decl(),
|
||||||
|
fns::stop::decl(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,6 +628,16 @@ mod fns {
|
|||||||
path_in: PathBuf,
|
path_in: PathBuf,
|
||||||
write: String,
|
write: String,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run writeFile in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
let (volumes, volume_path) = {
|
let (volumes, volume_path) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx: &JsContext = state.borrow();
|
let ctx: &JsContext = state.borrow();
|
||||||
@@ -677,6 +691,16 @@ mod fns {
|
|||||||
dst_volume: VolumeId,
|
dst_volume: VolumeId,
|
||||||
dst_path: PathBuf,
|
dst_path: PathBuf,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run rename in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
let (volumes, volume_path, volume_path_out) = {
|
let (volumes, volume_path, volume_path_out) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx: &JsContext = state.borrow();
|
let ctx: &JsContext = state.borrow();
|
||||||
@@ -734,6 +758,16 @@ mod fns {
|
|||||||
dst_path: PathBuf,
|
dst_path: PathBuf,
|
||||||
options: RsyncOptions,
|
options: RsyncOptions,
|
||||||
) -> Result<usize, AnyError> {
|
) -> Result<usize, AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run rsync in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
let (volumes, volume_path, volume_path_out, rsyncs) = {
|
let (volumes, volume_path, volume_path_out, rsyncs) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx: &JsContext = state.borrow();
|
let ctx: &JsContext = state.borrow();
|
||||||
@@ -828,12 +862,22 @@ mod fns {
|
|||||||
Ok(progress)
|
Ok(progress)
|
||||||
}
|
}
|
||||||
#[op]
|
#[op]
|
||||||
async fn set_permissions(
|
async fn chown(
|
||||||
state: Rc<RefCell<OpState>>,
|
state: Rc<RefCell<OpState>>,
|
||||||
volume_id: VolumeId,
|
volume_id: VolumeId,
|
||||||
path_in: PathBuf,
|
path_in: PathBuf,
|
||||||
readonly: bool,
|
ownership: u32,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run chown in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
let (volumes, volume_path) = {
|
let (volumes, volume_path) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx: &JsContext = state.borrow();
|
let ctx: &JsContext = state.borrow();
|
||||||
@@ -855,9 +899,56 @@ mod fns {
|
|||||||
volume_path.to_string_lossy(),
|
volume_path.to_string_lossy(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let mut perms = tokio::fs::metadata(&new_file).await?.permissions();
|
let output = tokio::process::Command::new("chown")
|
||||||
perms.set_readonly(readonly);
|
.arg("--recursive")
|
||||||
tokio::fs::set_permissions(new_file, perms).await?;
|
.arg(format!("{ownership}"))
|
||||||
|
.arg(new_file.as_os_str())
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(anyhow!("Chown Error"));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[op]
|
||||||
|
async fn chmod(
|
||||||
|
state: Rc<RefCell<OpState>>,
|
||||||
|
volume_id: VolumeId,
|
||||||
|
path_in: PathBuf,
|
||||||
|
mode: u32,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run chmod in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
let (volumes, volume_path) = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
let volume_path = ctx
|
||||||
|
.volumes
|
||||||
|
.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id)
|
||||||
|
.ok_or_else(|| anyhow!("There is no {} in volumes", volume_id))?;
|
||||||
|
(ctx.volumes.clone(), volume_path)
|
||||||
|
};
|
||||||
|
if volumes.readonly(&volume_id) {
|
||||||
|
bail!("Volume {} is readonly", volume_id);
|
||||||
|
}
|
||||||
|
let new_file = volume_path.join(path_in);
|
||||||
|
// With the volume check
|
||||||
|
if !is_subset(&volume_path, &new_file).await? {
|
||||||
|
bail!(
|
||||||
|
"Path '{}' has broken away from parent '{}'",
|
||||||
|
new_file.to_string_lossy(),
|
||||||
|
volume_path.to_string_lossy(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tokio::fs::set_permissions(new_file, Permissions::from_mode(mode)).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[op]
|
#[op]
|
||||||
@@ -866,6 +957,16 @@ mod fns {
|
|||||||
volume_id: VolumeId,
|
volume_id: VolumeId,
|
||||||
path_in: PathBuf,
|
path_in: PathBuf,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run removeFile in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
let (volumes, volume_path) = {
|
let (volumes, volume_path) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx: &JsContext = state.borrow();
|
let ctx: &JsContext = state.borrow();
|
||||||
@@ -897,6 +998,16 @@ mod fns {
|
|||||||
volume_id: VolumeId,
|
volume_id: VolumeId,
|
||||||
path_in: PathBuf,
|
path_in: PathBuf,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run removeDir in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
let (volumes, volume_path) = {
|
let (volumes, volume_path) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx: &JsContext = state.borrow();
|
let ctx: &JsContext = state.borrow();
|
||||||
@@ -928,6 +1039,16 @@ mod fns {
|
|||||||
volume_id: VolumeId,
|
volume_id: VolumeId,
|
||||||
path_in: PathBuf,
|
path_in: PathBuf,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run createDir in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
let (volumes, volume_path) = {
|
let (volumes, volume_path) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx: &JsContext = state.borrow();
|
let ctx: &JsContext = state.borrow();
|
||||||
@@ -1251,6 +1372,16 @@ mod fns {
|
|||||||
pid: u32,
|
pid: u32,
|
||||||
signal: u32,
|
signal: u32,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run sendSignal in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(rpc_client) = {
|
if let Some(rpc_client) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
@@ -1279,6 +1410,16 @@ mod fns {
|
|||||||
gid: u32,
|
gid: u32,
|
||||||
signal: u32,
|
signal: u32,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run signalGroup in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(rpc_client) = {
|
if let Some(rpc_client) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
@@ -1315,6 +1456,16 @@ mod fns {
|
|||||||
output: OutputStrategy,
|
output: OutputStrategy,
|
||||||
timeout: Option<u64>,
|
timeout: Option<u64>,
|
||||||
) -> Result<StartCommand, AnyError> {
|
) -> Result<StartCommand, AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run command in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
if let (gid, Some(rpc_client)) = {
|
if let (gid, Some(rpc_client)) = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
@@ -1390,99 +1541,6 @@ mod fns {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op]
|
|
||||||
async fn chown(
|
|
||||||
state: Rc<RefCell<OpState>>,
|
|
||||||
volume_id: VolumeId,
|
|
||||||
path_in: PathBuf,
|
|
||||||
ownership: u32,
|
|
||||||
) -> Result<(), AnyError> {
|
|
||||||
let sandboxed = {
|
|
||||||
let state = state.borrow();
|
|
||||||
let ctx: &JsContext = state.borrow();
|
|
||||||
ctx.sandboxed
|
|
||||||
};
|
|
||||||
|
|
||||||
if sandboxed {
|
|
||||||
bail!("Will not run chown in sandboxed mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
let (volumes, volume_path) = {
|
|
||||||
let state = state.borrow();
|
|
||||||
let ctx: &JsContext = state.borrow();
|
|
||||||
let volume_path = ctx
|
|
||||||
.volumes
|
|
||||||
.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id)
|
|
||||||
.ok_or_else(|| anyhow!("There is no {} in volumes", volume_id))?;
|
|
||||||
(ctx.volumes.clone(), volume_path)
|
|
||||||
};
|
|
||||||
if volumes.readonly(&volume_id) {
|
|
||||||
bail!("Volume {} is readonly", volume_id);
|
|
||||||
}
|
|
||||||
let path_in = path_in.strip_prefix("/").unwrap_or(&path_in);
|
|
||||||
let new_file = volume_path.join(path_in);
|
|
||||||
// With the volume check
|
|
||||||
if !is_subset(&volume_path, &new_file).await? {
|
|
||||||
bail!(
|
|
||||||
"Path '{}' has broken away from parent '{}'",
|
|
||||||
new_file.to_string_lossy(),
|
|
||||||
volume_path.to_string_lossy(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let output = tokio::process::Command::new("chown")
|
|
||||||
.arg("--recursive")
|
|
||||||
.arg(format!("{ownership}"))
|
|
||||||
.arg(new_file.as_os_str())
|
|
||||||
.output()
|
|
||||||
.await?;
|
|
||||||
if !output.status.success() {
|
|
||||||
return Err(anyhow!("Chown Error"));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[op]
|
|
||||||
async fn chmod(
|
|
||||||
state: Rc<RefCell<OpState>>,
|
|
||||||
volume_id: VolumeId,
|
|
||||||
path_in: PathBuf,
|
|
||||||
mode: u32,
|
|
||||||
) -> Result<(), AnyError> {
|
|
||||||
let sandboxed = {
|
|
||||||
let state = state.borrow();
|
|
||||||
let ctx: &JsContext = state.borrow();
|
|
||||||
ctx.sandboxed
|
|
||||||
};
|
|
||||||
|
|
||||||
if sandboxed {
|
|
||||||
bail!("Will not run chmod in sandboxed mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
let (volumes, volume_path) = {
|
|
||||||
let state = state.borrow();
|
|
||||||
let ctx: &JsContext = state.borrow();
|
|
||||||
let volume_path = ctx
|
|
||||||
.volumes
|
|
||||||
.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id)
|
|
||||||
.ok_or_else(|| anyhow!("There is no {} in volumes", volume_id))?;
|
|
||||||
(ctx.volumes.clone(), volume_path)
|
|
||||||
};
|
|
||||||
if volumes.readonly(&volume_id) {
|
|
||||||
bail!("Volume {} is readonly", volume_id);
|
|
||||||
}
|
|
||||||
let path_in = path_in.strip_prefix("/").unwrap_or(&path_in);
|
|
||||||
let new_file = volume_path.join(path_in);
|
|
||||||
// With the volume check
|
|
||||||
if !is_subset(&volume_path, &new_file).await? {
|
|
||||||
bail!(
|
|
||||||
"Path '{}' has broken away from parent '{}'",
|
|
||||||
new_file.to_string_lossy(),
|
|
||||||
volume_path.to_string_lossy(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
tokio::fs::set_permissions(new_file, Permissions::from_mode(mode)).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
async fn get_service_config(
|
async fn get_service_config(
|
||||||
state: Rc<RefCell<OpState>>,
|
state: Rc<RefCell<OpState>>,
|
||||||
@@ -1510,6 +1568,16 @@ mod fns {
|
|||||||
internal_port: u16,
|
internal_port: u16,
|
||||||
address_schema: AddressSchemaOnion,
|
address_schema: AddressSchemaOnion,
|
||||||
) -> Result<helpers::Address, AnyError> {
|
) -> Result<helpers::Address, AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run bindOnion in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
let os = {
|
let os = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
@@ -1525,6 +1593,16 @@ mod fns {
|
|||||||
internal_port: u16,
|
internal_port: u16,
|
||||||
address_schema: AddressSchemaLocal,
|
address_schema: AddressSchemaLocal,
|
||||||
) -> Result<helpers::Address, AnyError> {
|
) -> Result<helpers::Address, AnyError> {
|
||||||
|
let sandboxed = {
|
||||||
|
let state = state.borrow();
|
||||||
|
let ctx: &JsContext = state.borrow();
|
||||||
|
ctx.sandboxed
|
||||||
|
};
|
||||||
|
|
||||||
|
if sandboxed {
|
||||||
|
bail!("Will not run bindLocal in sandboxed mode");
|
||||||
|
}
|
||||||
|
|
||||||
let os = {
|
let os = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
@@ -1536,12 +1614,12 @@ mod fns {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
fn set_started(state: &mut OpState) -> Result<(), AnyError> {
|
fn set_started(state: &mut OpState) {
|
||||||
let os = {
|
let os = {
|
||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
ctx.os.clone()
|
ctx.os.clone()
|
||||||
};
|
};
|
||||||
os.set_started().map_err(|e| anyhow!("{e:?}"))
|
os.set_started()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
@@ -1561,7 +1639,9 @@ mod fns {
|
|||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
ctx.os.clone()
|
ctx.os.clone()
|
||||||
};
|
};
|
||||||
os.restart().await.map_err(|e| anyhow!("{e:?}"))
|
os.restart().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
@@ -1581,7 +1661,9 @@ mod fns {
|
|||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
ctx.os.clone()
|
ctx.os.clone()
|
||||||
};
|
};
|
||||||
os.start().await.map_err(|e| anyhow!("{e:?}"))
|
os.start().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
@@ -1601,7 +1683,9 @@ mod fns {
|
|||||||
let ctx = state.borrow::<JsContext>();
|
let ctx = state.borrow::<JsContext>();
|
||||||
ctx.os.clone()
|
ctx.os.clone()
|
||||||
};
|
};
|
||||||
os.stop().await.map_err(|e| anyhow!("{e:?}"))
|
os.stop().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We need to make sure that during the file accessing, we don't reach beyond our scope of control
|
/// We need to make sure that during the file accessing, we don't reach beyond our scope of control
|
||||||
|
|||||||
Reference in New Issue
Block a user