appmgr 0.3.0 rewrite pt 1

appmgr: split bins

update cargo.toml and .gitignore

context

appmgr: refactor error module

appmgr: context

begin new s9pk format

appmgr: add fields to manifest

appmgr: start action abstraction

appmgr: volume abstraction

appmgr: improved volumes

appmgr: install wip

appmgr: health daemon

appmgr: health checks

appmgr: wip

config get

appmgr: secret store

wip

appmgr: config rewritten

appmgr: delete non-reusable code

appmgr: wip

appmgr: please the borrow-checker

appmgr: technically runs now

appmgr: cli

appmgr: clean up cli

appmgr: rpc-toolkit in action

appmgr: wrap up config

appmgr: account for updates during install

appmgr: fix: #308

appmgr: impl Display for Version

appmgr: cleanup

appmgr: set dependents on install

appmgr: dependency health checks
This commit is contained in:
Aiden McClelland
2021-04-08 11:16:25 -06:00
committed by Aiden McClelland
parent 5741cf084f
commit 8954e3e338
84 changed files with 7510 additions and 9950 deletions

View File

@@ -1,68 +1,63 @@
use std::cmp::Ordering;
use async_trait::async_trait;
use failure::ResultExt as _;
use futures::stream::TryStreamExt;
use lazy_static::lazy_static;
use patch_db::DbHandle;
use rpc_toolkit::command;
use tokio_compat_02::FutureExt;
use crate::util::{to_yaml_async_writer, AsyncCompat, PersistencePath};
use crate::Error;
use crate::ResultExt as _;
// mod v0_1_0;
// mod v0_1_1;
// mod v0_1_2;
// mod v0_1_3;
// mod v0_1_4;
// mod v0_1_5;
// mod v0_2_0;
// mod v0_2_1;
// mod v0_2_2;
// mod v0_2_3;
// mod v0_2_4;
// mod v0_2_5;
// mod v0_2_6;
// mod v0_2_7;
// mod v0_2_8;
// mod v0_2_9;
mod v0_1_0;
mod v0_1_1;
mod v0_1_2;
mod v0_1_3;
mod v0_1_4;
mod v0_1_5;
mod v0_2_0;
mod v0_2_1;
mod v0_2_2;
mod v0_2_3;
mod v0_2_4;
mod v0_2_5;
mod v0_2_6;
mod v0_2_7;
mod v0_2_8;
mod v0_2_9;
// mod v0_2_10;
// mod v0_2_11;
// mod v0_2_12;
mod v0_2_10;
mod v0_2_11;
mod v0_2_12;
mod v0_2_13;
mod v0_2_14;
mod v0_2_15;
mod v0_2_16;
// pub use v0_2_12::Version as Current;
pub type Current = ();
pub use v0_2_16::Version as Current;
use crate::context::{CliContext, EitherContext, RpcContext};
use crate::util::{to_yaml_async_writer, AsyncCompat};
use crate::{Error, ResultExt as _};
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum Version {
V0_0_0(Wrapper<()>),
V0_1_0(Wrapper<v0_1_0::Version>),
V0_1_1(Wrapper<v0_1_1::Version>),
V0_1_2(Wrapper<v0_1_2::Version>),
V0_1_3(Wrapper<v0_1_3::Version>),
V0_1_4(Wrapper<v0_1_4::Version>),
V0_1_5(Wrapper<v0_1_5::Version>),
V0_2_0(Wrapper<v0_2_0::Version>),
V0_2_1(Wrapper<v0_2_1::Version>),
V0_2_2(Wrapper<v0_2_2::Version>),
V0_2_3(Wrapper<v0_2_3::Version>),
V0_2_4(Wrapper<v0_2_4::Version>),
V0_2_5(Wrapper<v0_2_5::Version>),
V0_2_6(Wrapper<v0_2_6::Version>),
V0_2_7(Wrapper<v0_2_7::Version>),
V0_2_8(Wrapper<v0_2_8::Version>),
V0_2_9(Wrapper<v0_2_9::Version>),
V0_2_10(Wrapper<v0_2_10::Version>),
V0_2_11(Wrapper<v0_2_11::Version>),
V0_2_12(Wrapper<v0_2_12::Version>),
V0_2_13(Wrapper<v0_2_13::Version>),
V0_2_14(Wrapper<v0_2_14::Version>),
V0_2_15(Wrapper<v0_2_15::Version>),
V0_2_16(Wrapper<v0_2_16::Version>),
// V0_1_0(Wrapper<v0_1_0::Version>),
// V0_1_1(Wrapper<v0_1_1::Version>),
// V0_1_2(Wrapper<v0_1_2::Version>),
// V0_1_3(Wrapper<v0_1_3::Version>),
// V0_1_4(Wrapper<v0_1_4::Version>),
// V0_1_5(Wrapper<v0_1_5::Version>),
// V0_2_0(Wrapper<v0_2_0::Version>),
// V0_2_1(Wrapper<v0_2_1::Version>),
// V0_2_2(Wrapper<v0_2_2::Version>),
// V0_2_3(Wrapper<v0_2_3::Version>),
// V0_2_4(Wrapper<v0_2_4::Version>),
// V0_2_5(Wrapper<v0_2_5::Version>),
// V0_2_6(Wrapper<v0_2_6::Version>),
// V0_2_7(Wrapper<v0_2_7::Version>),
// V0_2_8(Wrapper<v0_2_8::Version>),
// V0_2_9(Wrapper<v0_2_9::Version>),
// V0_2_10(Wrapper<v0_2_10::Version>),
// V0_2_11(Wrapper<v0_2_11::Version>),
// V0_2_12(Wrapper<v0_2_12::Version>),
Other(emver::Version),
}
@@ -73,39 +68,62 @@ where
{
type Previous: VersionT;
fn new() -> Self;
fn semver(&self) -> &'static emver::Version;
async fn up(&self) -> Result<(), Error>;
async fn down(&self) -> Result<(), Error>;
async fn commit(&self) -> Result<(), Error> {
let mut out = PersistencePath::from_ref("version").write(None).await?;
to_yaml_async_writer(out.as_mut(), &self.semver()).await?;
out.commit().await?;
fn semver(&self) -> &'static crate::util::Version;
async fn up<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error>;
async fn down<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error>;
async fn commit<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> {
crate::db::DatabaseModel::new()
.server_info()
.version()
.put(db, self.semver())
.await?;
Ok(())
}
async fn migrate_to<V: VersionT>(&self, version: &V) -> Result<(), Error> {
async fn migrate_to<V: VersionT, Db: DbHandle>(
&self,
version: &V,
db: &mut Db,
) -> Result<(), Error> {
match self.semver().cmp(version.semver()) {
Ordering::Greater => self.rollback_to_unchecked(version).await,
Ordering::Less => version.migrate_from_unchecked(self).await,
Ordering::Greater => self.rollback_to_unchecked(version, db).await,
Ordering::Less => version.migrate_from_unchecked(self, db).await,
Ordering::Equal => Ok(()),
}
}
async fn migrate_from_unchecked<V: VersionT>(&self, version: &V) -> Result<(), Error> {
async fn migrate_from_unchecked<V: VersionT, Db: DbHandle>(
&self,
version: &V,
db: &mut Db,
) -> Result<(), Error> {
let previous = Self::Previous::new();
if version.semver() != previous.semver() {
previous.migrate_from_unchecked(version).await?;
previous.migrate_from_unchecked(version, db).await?;
}
log::info!("{} -> {}", previous.semver(), self.semver());
self.up().await?;
self.commit().await?;
log::info!(
"{} -> {}",
previous.semver().as_str(),
self.semver().as_str()
);
self.up(db).await?;
self.commit(db).await?;
Ok(())
}
async fn rollback_to_unchecked<V: VersionT>(&self, version: &V) -> Result<(), Error> {
async fn rollback_to_unchecked<V: VersionT, Db: DbHandle>(
&self,
version: &V,
db: &mut Db,
) -> Result<(), Error> {
let previous = Self::Previous::new();
log::info!("{} -> {}", self.semver(), previous.semver());
self.down().await?;
previous.commit().await?;
log::info!(
"{} -> {}",
self.semver().as_str(),
previous.semver().as_str()
);
self.down(db).await?;
previous.commit(db).await?;
if version.semver() != previous.semver() {
previous.rollback_to_unchecked(version).await?;
previous.rollback_to_unchecked(version, db).await?;
}
Ok(())
}
@@ -124,7 +142,7 @@ where
T: VersionT,
{
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let v = emver::Version::deserialize(deserializer)?;
let v = crate::util::Version::deserialize(deserializer)?;
let version = T::new();
if &v == version.semver() {
Ok(Wrapper(version))
@@ -133,166 +151,38 @@ where
}
}
}
const V0_0_0: emver::Version = emver::Version::new(0, 0, 0, 0);
lazy_static! {
static ref V0_0_0: crate::util::Version = emver::Version::new(0, 0, 0, 0).into();
}
#[async_trait]
impl VersionT for () {
type Previous = ();
fn new() -> Self {
()
}
fn semver(&self) -> &'static emver::Version {
&V0_0_0
fn semver(&self) -> &'static crate::util::Version {
&*V0_0_0
}
async fn up(&self) -> Result<(), Error> {
async fn up<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> {
Ok(())
}
async fn down(&self) -> Result<(), Error> {
async fn down<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> {
Ok(())
}
}
pub async fn init() -> Result<(), failure::Error> {
let _lock = PersistencePath::from_ref("").lock(true).await?;
let vpath = PersistencePath::from_ref("version");
if let Some(mut f) = vpath.maybe_read(false).await.transpose()? {
let v: Version = crate::util::from_yaml_async_reader(&mut *f).await?;
match v {
Version::V0_0_0(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_1_0(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_1_1(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_1_2(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_1_3(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_1_4(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_1_5(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_0(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_1(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_2(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_3(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_4(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_5(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_6(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_7(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_8(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_9(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_10(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_11(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_12(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_13(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_14(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_15(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_16(v) => v.0.migrate_to(&Current::new()).await?,
Version::Other(_) => (),
// TODO find some way to automate this?
}
} else {
().migrate_to(&Current::new()).await?;
}
Ok(())
pub async fn init() -> Result<(), Error> {
todo!()
}
pub async fn self_update(requirement: emver::VersionRange) -> Result<(), Error> {
let req_str: String = format!("{}", requirement)
.chars()
.filter(|c| !c.is_whitespace())
.collect();
let url = format!("{}/appmgr?spec={}", &*crate::SYS_REGISTRY_URL, req_str);
log::info!("Fetching new version from {}", url);
let response = reqwest::get(&url)
.compat()
.await
.with_code(crate::error::NETWORK_ERROR)?
.error_for_status()
.with_code(crate::error::REGISTRY_ERROR)?;
let tmp_appmgr_path = PersistencePath::from_ref("appmgr").tmp();
if let Some(parent) = tmp_appmgr_path.parent() {
if !parent.exists() {
tokio::fs::create_dir_all(parent)
.await
.with_code(crate::error::FILESYSTEM_ERROR)?;
}
}
let mut f = tokio::fs::OpenOptions::new()
.create(true)
.write(true)
.open(&tmp_appmgr_path)
.await
.with_context(|e| format!("{}: {}", tmp_appmgr_path.display(), e))
.with_code(crate::error::FILESYSTEM_ERROR)?;
tokio::io::copy(
&mut AsyncCompat(
response
.bytes_stream()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
.into_async_read(),
),
&mut f,
)
.await
.no_code()?;
drop(f);
crate::ensure_code!(
tokio::process::Command::new("chmod")
.arg("700")
.arg(&tmp_appmgr_path)
.output()
.await?
.status
.success(),
crate::error::FILESYSTEM_ERROR,
"chmod failed"
);
let out = std::process::Command::new(&tmp_appmgr_path)
.arg("semver")
.stdout(std::process::Stdio::piped())
.spawn()?
.wait_with_output()
.with_context(|e| format!("{} semver: {}", tmp_appmgr_path.display(), e))
.no_code()?;
let out_str = std::str::from_utf8(&out.stdout).no_code()?;
log::info!("Migrating to version {}", out_str);
let v: Version = serde_yaml::from_str(out_str)
.with_context(|e| format!("{}: {:?}", e, out_str))
.with_code(crate::error::SERDE_ERROR)?;
match v {
Version::V0_0_0(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_1_0(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_1_1(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_1_2(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_1_3(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_1_4(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_1_5(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_0(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_1(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_2(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_3(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_4(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_5(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_6(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_7(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_8(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_9(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_10(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_11(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_12(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_13(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_14(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_15(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_16(v) => Current::new().migrate_to(&v.0).await?,
Version::Other(_) => (),
// TODO find some way to automate this?
};
let cur_path = std::path::Path::new("/usr/local/bin/appmgr");
tokio::fs::rename(&tmp_appmgr_path, &cur_path)
.await
.with_context(|e| {
format!(
"{} -> {}: {}",
tmp_appmgr_path.display(),
cur_path.display(),
e
)
})
.with_code(crate::error::FILESYSTEM_ERROR)?;
Ok(())
todo!()
}
#[command(rename = "git-info", local)]
pub fn git_info(#[context] _ctx: EitherContext) -> Result<String, Error> {
Ok(
git_version::git_version!(args = ["--always", "--abbrev=40", "--dirty=-modified"])
.to_owned(),
)
}

View File

@@ -1,279 +0,0 @@
use std::path::Path;
use super::*;
const V0_1_0: emver::Version = emver::Version::new(0, 1, 0, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = ();
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_1_0
}
async fn up(&self) -> Result<(), Error> {
tokio::fs::create_dir_all(Path::new(crate::PERSISTENCE_DIR).join("tor")).await?;
tokio::fs::create_dir_all(Path::new(crate::PERSISTENCE_DIR).join("apps")).await?;
tokio::fs::create_dir_all(Path::new(crate::TMP_DIR).join("tor")).await?;
tokio::fs::create_dir_all(Path::new(crate::TMP_DIR).join("apps")).await?;
let mut outfile = legacy::util::PersistencePath::from_ref("tor/torrc")
.write()
.await?;
tokio::io::copy(
&mut AsyncCompat(
reqwest::get(&format!("{}/torrc?spec==0.0.0", &*crate::SYS_REGISTRY_URL))
.compat()
.await
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
.with_code(crate::error::NETWORK_ERROR)?
.error_for_status()
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
.with_code(crate::error::REGISTRY_ERROR)?
.bytes_stream()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
.into_async_read(),
),
outfile.as_mut(),
)
.await
.with_code(crate::error::FILESYSTEM_ERROR)?;
outfile.commit().await?;
legacy::tor::set_svc(
"start9-agent",
legacy::tor::Service {
ports: vec![5959],
hidden_service_version: Default::default(),
},
)
.await
.no_code()?;
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}
mod legacy {
pub mod tor {
use failure::{Error, ResultExt};
use linear_map::LinearMap;
use tokio::io::AsyncWriteExt;
use crate::tor::HiddenServiceVersion;
use super::util::PersistencePath;
pub const ETC_TOR_RC: &'static str = "/etc/tor/torrc";
pub const HIDDEN_SERVICE_DIR_ROOT: &'static str = "/var/lib/tor";
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct Service {
pub ports: Vec<u16>,
pub hidden_service_version: HiddenServiceVersion,
}
async fn services_map(path: &PersistencePath) -> Result<LinearMap<String, Service>, Error> {
use crate::util::Apply;
Ok(path
.maybe_read()
.await
.transpose()?
.map(crate::util::from_yaml_async_reader)
.apply(futures::future::OptionFuture::from)
.await
.transpose()?
.unwrap_or_else(LinearMap::new))
}
pub async fn write_services(
hidden_services: &LinearMap<String, Service>,
) -> Result<(), Error> {
tokio::fs::copy(crate::TOR_RC, ETC_TOR_RC)
.await
.with_context(|e| format!("{} -> {}: {}", crate::TOR_RC, ETC_TOR_RC, e))?;
let mut f = tokio::fs::OpenOptions::new()
.append(true)
.open(ETC_TOR_RC)
.await?;
f.write("\n".as_bytes()).await?;
for (name, service) in hidden_services {
f.write("\n".as_bytes()).await?;
f.write(format!("# HIDDEN SERVICE FOR {}\n", name).as_bytes())
.await?;
f.write(
format!(
"HiddenServiceDir {}/app-{}/\n",
HIDDEN_SERVICE_DIR_ROOT, name
)
.as_bytes(),
)
.await?;
f.write(format!("{}\n", service.hidden_service_version).as_bytes())
.await?;
for port in &service.ports {
f.write(format!("HiddenServicePort {} 127.0.0.1:{}\n", port, port).as_bytes())
.await?;
}
f.write("\n".as_bytes()).await?;
}
Ok(())
}
pub async fn set_svc(name: &str, service: Service) -> Result<(), Error> {
log::info!(
"Adding Tor hidden service {} to {}.",
name,
crate::SERVICES_YAML
);
let path = PersistencePath::from_ref(crate::SERVICES_YAML);
let mut hidden_services = services_map(&path).await?;
hidden_services.insert(name.to_owned(), service);
let mut services_yaml = path.write().await?;
crate::util::to_yaml_async_writer(services_yaml.as_mut(), &hidden_services).await?;
services_yaml.write_all("\n".as_bytes()).await?;
services_yaml.commit().await?;
log::info!("Adding Tor hidden service {} to {}.", name, ETC_TOR_RC);
write_services(&hidden_services).await?;
log::info!("Restarting Tor.");
let svc_exit = std::process::Command::new("service")
.args(&["tor", "restart"])
.status()?;
ensure!(
svc_exit.success(),
"Failed to Restart Tor: {}",
svc_exit.code().unwrap_or(0)
);
Ok(())
}
}
pub mod util {
use std::path::{Path, PathBuf};
use tokio::fs::File;
use crate::Error;
use crate::ResultExt as _;
use failure::ResultExt as _;
#[derive(Clone, Debug)]
pub struct PersistencePath(PathBuf);
impl PersistencePath {
pub fn from_ref<P: AsRef<Path>>(p: P) -> Self {
let path = p.as_ref();
PersistencePath(if path.has_root() {
path.strip_prefix("/").unwrap().to_owned()
} else {
path.to_owned()
})
}
pub fn tmp(&self) -> PathBuf {
Path::new(crate::TMP_DIR).join(&self.0)
}
pub fn path(&self) -> PathBuf {
Path::new(crate::PERSISTENCE_DIR).join(&self.0)
}
pub async fn maybe_read(&self) -> Option<Result<File, Error>> {
let path = self.path();
if path.exists() {
Some(
File::open(&path)
.await
.with_context(|e| format!("{}: {}", path.display(), e))
.with_code(crate::error::FILESYSTEM_ERROR),
)
} else {
None
}
}
pub async fn write(&self) -> Result<PersistenceFile, Error> {
let path = self.path();
if let Some(parent) = path.parent() {
if !parent.exists() {
tokio::fs::create_dir_all(parent).await?;
}
}
Ok(if path.exists() {
let path = self.tmp();
if let Some(parent) = path.parent() {
if !parent.exists() {
tokio::fs::create_dir_all(parent).await?;
}
}
PersistenceFile::new(File::create(path).await?, Some(self.clone()))
} else {
PersistenceFile::new(File::create(path).await?, None)
})
}
}
#[derive(Debug)]
pub struct PersistenceFile {
file: File,
needs_commit: Option<PersistencePath>,
}
impl PersistenceFile {
pub fn new(file: File, needs_commit: Option<PersistencePath>) -> Self {
PersistenceFile { file, needs_commit }
}
/// Commits the file to the persistence directory.
/// If this fails, the file was not saved.
pub async fn commit(mut self) -> Result<(), Error> {
if let Some(path) = self.needs_commit.take() {
tokio::fs::rename(path.tmp(), path.path())
.await
.with_context(|e| {
format!(
"{} -> {}: {}",
path.tmp().display(),
path.path().display(),
e
)
})
.with_code(crate::error::FILESYSTEM_ERROR)
} else {
Ok(())
}
}
}
impl std::ops::Deref for PersistenceFile {
type Target = File;
fn deref(&self) -> &Self::Target {
&self.file
}
}
impl std::ops::DerefMut for PersistenceFile {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.file
}
}
impl AsRef<File> for PersistenceFile {
fn as_ref(&self) -> &File {
&*self
}
}
impl AsMut<File> for PersistenceFile {
fn as_mut(&mut self) -> &mut File {
&mut *self
}
}
impl Drop for PersistenceFile {
fn drop(&mut self) {
if let Some(path) = &self.needs_commit {
log::warn!(
"{} was dropped without being committed.",
path.path().display()
);
}
}
}
}
}

View File

@@ -1,204 +0,0 @@
use std::path::Path;
use super::*;
const V0_1_1: emver::Version = emver::Version::new(0, 1, 1, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_1_0::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_1_1
}
async fn up(&self) -> Result<(), Error> {
log::info!("Update torrc");
let mut outfile = crate::util::PersistencePath::from_ref("tor/torrc")
.write(None)
.await?;
tokio::io::copy(
&mut AsyncCompat(
reqwest::get(&format!("{}/torrc?spec==0.1.1", &*crate::SYS_REGISTRY_URL))
.compat()
.await
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
.with_code(crate::error::NETWORK_ERROR)?
.error_for_status()
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
.with_code(crate::error::REGISTRY_ERROR)?
.bytes_stream()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
.into_async_read(),
),
outfile.as_mut(),
)
.await
.with_code(crate::error::FILESYSTEM_ERROR)?;
outfile.commit().await?;
if !std::process::Command::new("docker")
.arg("network")
.arg("create")
.arg("-d")
.arg("bridge")
.arg("--subnet=172.18.0.0/16")
.arg("start9")
.stdout(std::process::Stdio::null())
.status()?
.success()
{
log::warn!("Failed to Create Network")
}
match tokio::fs::remove_file(Path::new(crate::PERSISTENCE_DIR).join(crate::SERVICES_YAML))
.await
{
Ok(_) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(e),
}
.with_context(|e| format!("{}/{}: {}", crate::PERSISTENCE_DIR, crate::SERVICES_YAML, e))
.with_code(crate::error::FILESYSTEM_ERROR)?;
crate::tor::reload().await?;
for app in crate::apps::list_info().await? {
legacy::update::update(&app.0).await?;
}
Ok(())
}
async fn down(&self) -> Result<(), Error> {
let mut outfile = crate::util::PersistencePath::from_ref("tor/torrc")
.write(None)
.await?;
tokio::io::copy(
&mut AsyncCompat(
reqwest::get(&format!("{}/torrc?spec==0.1.0", &*crate::SYS_REGISTRY_URL))
.compat()
.await
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
.no_code()?
.error_for_status()
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
.no_code()?
.bytes_stream()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
.into_async_read(),
),
outfile.as_mut(),
)
.await
.with_code(crate::error::FILESYSTEM_ERROR)?;
outfile.commit().await?;
for app in crate::apps::list_info().await? {
legacy::remove::remove(&app.0, false).await?;
}
let tor_svcs = crate::util::PersistencePath::from_ref(crate::SERVICES_YAML).path();
if tor_svcs.exists() {
tokio::fs::remove_file(&tor_svcs)
.await
.with_context(|e| format!("{}: {}", tor_svcs.display(), e))
.with_code(crate::error::FILESYSTEM_ERROR)?;
}
if !std::process::Command::new("docker")
.arg("network")
.arg("rm")
.arg("start9")
.stdout(std::process::Stdio::null())
.status()?
.success()
{
log::warn!("Failed to Remove Network");
}
Ok(())
}
}
mod legacy {
pub mod remove {
use std::path::Path;
use crate::Error;
pub async fn remove(name: &str, purge: bool) -> Result<(), Error> {
log::info!("Removing app from manifest.");
crate::apps::remove(name).await?;
log::info!("Stopping docker container.");
if !tokio::process::Command::new("docker")
.args(&["stop", name])
.stdout(std::process::Stdio::null())
.stderr(match log::max_level() {
log::LevelFilter::Error => std::process::Stdio::null(),
_ => std::process::Stdio::inherit(),
})
.status()
.await?
.success()
{
log::error!("Failed to Stop Docker Container");
};
log::info!("Removing docker container.");
if !tokio::process::Command::new("docker")
.args(&["rm", name])
.stdout(std::process::Stdio::null())
.stderr(match log::max_level() {
log::LevelFilter::Error => std::process::Stdio::null(),
_ => std::process::Stdio::inherit(),
})
.status()
.await?
.success()
{
log::error!("Failed to Remove Docker Container");
};
if purge {
log::info!("Removing tor hidden service.");
crate::tor::rm_svc(name).await?;
log::info!("Removing app metadata.");
std::fs::remove_dir_all(Path::new(crate::PERSISTENCE_DIR).join("apps").join(name))?;
log::info!("Destroying mounted volume.");
std::fs::remove_dir_all(Path::new(crate::VOLUMES).join(name))?;
log::info!("Pruning unused docker images.");
crate::ensure_code!(
std::process::Command::new("docker")
.args(&["image", "prune", "-a", "-f"])
.stdout(std::process::Stdio::null())
.stderr(match log::max_level() {
log::LevelFilter::Error => std::process::Stdio::null(),
_ => std::process::Stdio::inherit(),
})
.status()?
.success(),
3,
"Failed to Prune Docker Images"
);
};
Ok(())
}
}
pub mod update {
use crate::Error;
pub async fn update(name_version: &str) -> Result<(), Error> {
let name = name_version
.split("@")
.next()
.ok_or_else(|| failure::format_err!("invalid app id"))?;
crate::install::download_name(name_version).await?;
super::remove::remove(name, false).await?;
crate::install::install_name(name_version, true).await?;
let config = crate::apps::config(name).await?;
if let Some(cfg) = config.config {
if config.spec.matches(&cfg).is_ok() {
crate::apps::set_configured(name, true).await?;
}
}
Ok(())
}
}
}

View File

@@ -1,104 +0,0 @@
use futures::StreamExt;
use futures::TryStreamExt;
use linear_map::LinearMap;
use super::*;
const V0_1_2: emver::Version = emver::Version::new(0, 1, 2, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_1_1::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_1_2
}
async fn up(&self) -> Result<(), Error> {
let app_info = legacy::apps::list_info().await?;
for (name, _) in &app_info {
let p = PersistencePath::from_ref("apps")
.join(name)
.join("manifest.yaml");
let mut f = p.for_update().await?;
let manifest: crate::manifest::ManifestV0 = crate::util::from_yaml_async_reader(&mut f)
.await
.no_code()?;
let mut f = f.into_writer().await?;
crate::util::to_yaml_async_writer(&mut f, &crate::manifest::Manifest::V0(manifest))
.await
.no_code()?;
f.commit().await?;
}
let p = PersistencePath::from_ref("apps.yaml");
let exists = p.path().exists();
let mut f = p.for_update().await?;
let info: LinearMap<String, legacy::apps::AppInfo> = if exists {
crate::util::from_yaml_async_reader(&mut f)
.await
.no_code()?
} else {
LinearMap::new()
};
let new_info: LinearMap<String, crate::apps::AppInfo> = futures::stream::iter(info)
.then(|(name, i)| async move {
let title = crate::apps::manifest(&name).await?.title;
Ok::<_, Error>((
name,
crate::apps::AppInfo {
title,
version: i.version,
tor_address: i.tor_address,
configured: i.configured,
recoverable: false,
needs_restart: false,
},
))
})
.try_collect()
.await?;
let mut f = f.into_writer().await?;
crate::util::to_yaml_async_writer(&mut f, &new_info)
.await
.no_code()?;
f.commit().await?;
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}
mod legacy {
pub mod apps {
use linear_map::LinearMap;
use crate::util::from_yaml_async_reader;
use crate::util::Apply;
use crate::util::PersistencePath;
use crate::Error;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct AppInfo {
pub version: emver::Version,
pub tor_address: Option<String>,
pub configured: bool,
}
pub async fn list_info() -> Result<LinearMap<String, AppInfo>, Error> {
let apps_path = PersistencePath::from_ref("apps.yaml");
Ok(apps_path
.maybe_read(false)
.await
.transpose()?
.map(|mut f| async move { from_yaml_async_reader(&mut *f).await })
.apply(futures::future::OptionFuture::from)
.await
.transpose()?
.unwrap_or_else(LinearMap::new))
}
}
}

View File

@@ -1,21 +0,0 @@
use super::*;
const V0_1_3: emver::Version = emver::Version::new(0, 1, 3, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_1_2::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_1_3
}
async fn up(&self) -> Result<(), Error> {
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,21 +0,0 @@
use super::*;
const V0_1_4: emver::Version = emver::Version::new(0, 1, 4, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_1_3::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_1_4
}
async fn up(&self) -> Result<(), Error> {
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,21 +0,0 @@
use super::*;
const V0_1_5: emver::Version = emver::Version::new(0, 1, 5, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_1_4::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_1_5
}
async fn up(&self) -> Result<(), Error> {
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,98 +0,0 @@
use linear_map::LinearMap;
use super::*;
use crate::util::{to_yaml_async_writer, PersistencePath};
const V0_2_0: emver::Version = emver::Version::new(0, 2, 0, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_1_5::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_0
}
async fn up(&self) -> Result<(), Error> {
let app_info: LinearMap<String, crate::apps::AppInfo> = legacy::apps::list_info()
.await?
.into_iter()
.map(|(id, ai)| {
(
id,
crate::apps::AppInfo {
title: ai.title,
version: ai.version,
tor_address: ai.tor_address,
configured: ai.configured,
recoverable: ai.recoverable,
needs_restart: false,
},
)
})
.collect();
let mut apps_file = PersistencePath::from_ref("apps.yaml").write(None).await?;
to_yaml_async_writer(&mut *apps_file, &app_info).await?;
apps_file.commit().await?;
Ok(())
}
async fn down(&self) -> Result<(), Error> {
let app_info: LinearMap<String, legacy::apps::AppInfo> = crate::apps::list_info()
.await?
.into_iter()
.map(|(id, ai)| {
(
id,
legacy::apps::AppInfo {
title: ai.title,
version: ai.version,
tor_address: ai.tor_address,
configured: ai.configured,
recoverable: ai.recoverable,
},
)
})
.collect();
let mut apps_file = PersistencePath::from_ref("apps.yaml").write(None).await?;
to_yaml_async_writer(&mut *apps_file, &app_info).await?;
apps_file.commit().await?;
Ok(())
}
}
mod legacy {
pub mod apps {
use linear_map::LinearMap;
use crate::util::{from_yaml_async_reader, PersistencePath};
use crate::Error;
fn not(b: &bool) -> bool {
!b
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct AppInfo {
pub title: String,
pub version: emver::Version,
pub tor_address: Option<String>,
pub configured: bool,
#[serde(default)]
#[serde(skip_serializing_if = "not")]
pub recoverable: bool,
}
pub async fn list_info() -> Result<LinearMap<String, AppInfo>, Error> {
let apps_path = PersistencePath::from_ref("apps.yaml");
let mut f = match apps_path.maybe_read(false).await.transpose()? {
Some(a) => a,
None => return Ok(LinearMap::new()),
};
from_yaml_async_reader(&mut *f).await
}
}
}

View File

@@ -1,21 +0,0 @@
use super::*;
const V0_2_1: emver::Version = emver::Version::new(0, 2, 1, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_0::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_1
}
async fn up(&self) -> Result<(), Error> {
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,21 +0,0 @@
use super::*;
const V0_2_10: emver::Version = emver::Version::new(0, 2, 10, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_9::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_10
}
async fn up(&self) -> Result<(), Error> {
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,38 +0,0 @@
use super::*;
use std::os::unix::process::ExitStatusExt;
const V0_2_11: emver::Version = emver::Version::new(0, 2, 11, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_10::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_11
}
async fn up(&self) -> Result<(), Error> {
crate::tor::write_lan_services(
&crate::tor::services_map(&PersistencePath::from_ref(crate::SERVICES_YAML)).await?,
)
.await?;
let svc_exit = std::process::Command::new("service")
.args(&["nginx", "reload"])
.status()?;
crate::ensure_code!(
svc_exit.success(),
crate::error::GENERAL_ERROR,
"Failed to Reload Nginx: {}",
svc_exit
.code()
.or_else(|| { svc_exit.signal().map(|a| 128 + a) })
.unwrap_or(0)
);
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,38 +0,0 @@
use super::*;
use std::os::unix::process::ExitStatusExt;
const V0_2_12: emver::Version = emver::Version::new(0, 2, 12, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_11::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_12
}
async fn up(&self) -> Result<(), Error> {
crate::tor::write_lan_services(
&crate::tor::services_map(&PersistencePath::from_ref(crate::SERVICES_YAML)).await?,
)
.await?;
let svc_exit = std::process::Command::new("service")
.args(&["nginx", "reload"])
.status()?;
crate::ensure_code!(
svc_exit.success(),
crate::error::GENERAL_ERROR,
"Failed to Reload Nginx: {}",
svc_exit
.code()
.or_else(|| { svc_exit.signal().map(|a| 128 + a) })
.unwrap_or(0)
);
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,21 +0,0 @@
use super::*;
const V0_2_2: emver::Version = emver::Version::new(0, 2, 2, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_1::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_2
}
async fn up(&self) -> Result<(), Error> {
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,21 +0,0 @@
use super::*;
const V0_2_3: emver::Version = emver::Version::new(0, 2, 3, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_2::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_3
}
async fn up(&self) -> Result<(), Error> {
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,21 +0,0 @@
use super::*;
const V0_2_4: emver::Version = emver::Version::new(0, 2, 4, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_3::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_4
}
async fn up(&self) -> Result<(), Error> {
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,21 +0,0 @@
use super::*;
const V0_2_5: emver::Version = emver::Version::new(0, 2, 5, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_4::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_5
}
async fn up(&self) -> Result<(), Error> {
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,21 +0,0 @@
use super::*;
const V0_2_6: emver::Version = emver::Version::new(0, 2, 6, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_5::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_6
}
async fn up(&self) -> Result<(), Error> {
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,36 +0,0 @@
use super::*;
use crate::util::Invoke;
const V0_2_7: emver::Version = emver::Version::new(0, 2, 7, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_6::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_7
}
async fn up(&self) -> Result<(), Error> {
for (app_id, _) in crate::apps::list_info().await? {
tokio::process::Command::new("docker")
.arg("stop")
.arg(&app_id)
.invoke("Docker")
.await?;
tokio::process::Command::new("docker")
.arg("update")
.arg("--restart")
.arg("no")
.arg(&app_id)
.invoke("Docker")
.await?;
}
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,36 +0,0 @@
use super::*;
use crate::util::Invoke;
const V0_2_8: emver::Version = emver::Version::new(0, 2, 8, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_7::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_8
}
async fn up(&self) -> Result<(), Error> {
for (app_id, _) in crate::apps::list_info().await? {
tokio::process::Command::new("docker")
.arg("stop")
.arg(&app_id)
.invoke("Docker")
.await?;
tokio::process::Command::new("docker")
.arg("update")
.arg("--restart")
.arg("no")
.arg(&app_id)
.invoke("Docker")
.await?;
}
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -1,75 +0,0 @@
use std::os::unix::process::ExitStatusExt;
use super::*;
const V0_2_9: emver::Version = emver::Version::new(0, 2, 9, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_8::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_9
}
async fn up(&self) -> Result<(), Error> {
crate::tor::write_lan_services(
&crate::tor::services_map(&PersistencePath::from_ref(crate::SERVICES_YAML)).await?,
)
.await?;
tokio::fs::os::unix::symlink(
crate::tor::ETC_NGINX_SERVICES_CONF,
"/etc/nginx/sites-enabled/start9-services.conf",
)
.await
.or_else(|e| {
if e.kind() == std::io::ErrorKind::AlreadyExists {
Ok(())
} else {
Err(e)
}
})?;
let svc_exit = std::process::Command::new("service")
.args(&["nginx", "reload"])
.status()?;
crate::ensure_code!(
svc_exit.success(),
crate::error::GENERAL_ERROR,
"Failed to Reload Nginx: {}",
svc_exit
.code()
.or_else(|| { svc_exit.signal().map(|a| 128 + a) })
.unwrap_or(0)
);
Ok(())
}
async fn down(&self) -> Result<(), Error> {
tokio::fs::remove_file("/etc/nginx/sites-enabled/start9-services.conf")
.await
.or_else(|e| match e {
e if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
e => Err(e),
})?;
tokio::fs::remove_file(crate::tor::ETC_NGINX_SERVICES_CONF)
.await
.or_else(|e| match e {
e if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
e => Err(e),
})?;
let svc_exit = std::process::Command::new("service")
.args(&["nginx", "reload"])
.status()?;
crate::ensure_code!(
svc_exit.success(),
crate::error::GENERAL_ERROR,
"Failed to Reload Nginx: {}",
svc_exit
.code()
.or_else(|| { svc_exit.signal().map(|a| 128 + a) })
.unwrap_or(0)
);
Ok(())
}
}