use bytes::Bytes;
use figment::providers::Env;
use log::{info, warn};
use serde;
use serde::{Deserialize, Serialize};
use std::io;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::fs;
use zip::ZipArchive;
use super::error::PrintNannySettingsError;
pub const DEFAULT_PRINTNANNY_USER: &str = "printnanny";
pub const PRINTNANNY_SETTINGS_FILENAME: &str = "printnanny.toml";
pub const DEFAULT_PRINTNANNY_SETTINGS_FILE: &str =
"/home/printnanny/.config/printnanny/vcs/printnanny/printnanny.toml";
pub const DEFAULT_PRINTNANNY_DATA_DIR: &str = "/home/printnanny/.local/share/printnanny";
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct PrintNannyPaths {
pub snapshot_dir: PathBuf,
pub state_dir: PathBuf, pub log_dir: PathBuf, pub run_dir: PathBuf,
pub issue_txt: PathBuf, pub os_release: PathBuf, }
impl Default for PrintNannyPaths {
fn default() -> Self {
let snapshot_dir: PathBuf = "/var/run/printnanny-snapshot".into();
let run_dir: PathBuf = "/var/run/printnanny".into();
let state_dir: PathBuf = DEFAULT_PRINTNANNY_DATA_DIR.into();
let issue_txt: PathBuf = "/etc/issue".into();
let log_dir: PathBuf = "/var/log/printnanny".into();
let os_release = "/etc/os-release".into();
Self {
snapshot_dir,
issue_txt,
state_dir,
log_dir,
os_release,
run_dir,
}
}
}
impl PrintNannyPaths {
pub fn cloud(&self) -> PathBuf {
self.data().join("PrintNannyCloudData.json")
}
pub fn state_lock(&self) -> PathBuf {
self.run_dir.join("state.lock")
}
pub fn settings_file(&self) -> PathBuf {
PathBuf::from(Env::var_or(
"PRINTNANNY_SETTINGS",
DEFAULT_PRINTNANNY_SETTINGS_FILE,
))
}
pub fn db(&self) -> PathBuf {
self.state_dir.join("db.sqlite")
}
pub fn creds(&self) -> PathBuf {
self.state_dir.join("creds")
}
pub fn data(&self) -> PathBuf {
self.state_dir.join("data")
}
pub fn events_socket(&self) -> PathBuf {
self.run_dir.join("events.socket")
}
pub fn cloud_nats_creds(&self) -> PathBuf {
self.creds().join("printnanny-cloud-nats.creds")
}
pub fn recovery(&self) -> PathBuf {
self.state_dir.join("recovery")
}
pub fn video(&self) -> PathBuf {
self.state_dir.join("video")
}
pub fn license_zip(&self) -> PathBuf {
self.creds().join("license.zip")
}
fn try_init(&self, dir: &Path) -> Result<(), io::Error> {
match dir.exists() {
true => {
info!("Skipping mkdir, already exists: {}", dir.display());
Ok(())
}
false => {
info!("Creating directory: {}", dir.display());
std::fs::create_dir_all(dir)
}
}
}
pub fn try_init_all(&self) -> Result<(), io::Error> {
let dirs = vec![self.creds(), self.data(), self.recovery(), self.video()];
for dir in dirs {
self.try_init(&dir)?;
}
Ok(())
}
pub fn try_load_nats_creds(&self) -> Result<String, std::io::Error> {
std::fs::read_to_string(self.cloud_nats_creds())
}
pub fn unpack_license(
&self,
backup: bool,
) -> Result<[(String, PathBuf); 1], PrintNannySettingsError> {
let license_zip = self.license_zip();
let file = match std::fs::File::open(&license_zip) {
Ok(f) => Ok(f),
Err(error) => Err(PrintNannySettingsError::ReadIOError {
path: license_zip.clone(),
error,
}),
}?;
info!("Unpacking {:?}", file);
let mut archive = ZipArchive::new(file)?;
let results = [(
"printnanny-cloud-nats.creds".to_string(),
self.cloud_nats_creds(),
)];
for (filename, dest) in results.iter() {
if dest.exists() {
match backup {
true => {
self.backup_file(dest)?;
}
false => {
warn!(
"{} already exists and backup=false, overwriting without backup",
&dest.display()
);
}
}
}
let mut file = archive.by_name(filename).map_err(|_e| {
PrintNannySettingsError::ArchiveMissingFile {
filename: filename.to_string(),
archive: license_zip.clone(),
}
})?;
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => Ok(()),
Err(error) => Err(PrintNannySettingsError::ReadIOError {
path: PathBuf::from(filename),
error,
}),
}?;
match std::fs::write(dest, &contents) {
Ok(_) => {
info!("Wrote seed file {:?}", dest);
Ok(())
}
Err(error) => Err(PrintNannySettingsError::WriteIOError {
path: PathBuf::from(filename),
error,
}),
}?;
}
Ok(results)
}
pub fn backup_file(&self, filename: &PathBuf) -> Result<PathBuf, PrintNannySettingsError> {
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let new_filename = format!("{}.{}.bak", filename.display(), ts);
let new_filepath = PathBuf::from(&new_filename);
std::fs::copy(filename, &new_filepath)?;
info!(
"{} already exists, backed up to {} before overwriting",
filename.display(),
new_filepath.display()
);
Ok(new_filepath)
}
pub async fn write_license_zip(
&self,
b: Bytes,
backup: bool,
) -> Result<(), PrintNannySettingsError> {
let filename = self.license_zip();
if filename.exists() {
match backup {
true => {
self.backup_file(&filename)?;
}
false => {
warn!(
"{} already exists and backup=false, overwriting without backup",
filename.display()
);
}
}
}
fs::write(filename, b).await?;
Ok(())
}
pub fn crash_report_paths(&self) -> Vec<PathBuf> {
vec![
PathBuf::from("/var/log/cloud-init.log"),
PathBuf::from("/var/log/nginx/access.log"),
PathBuf::from("/var/log/nginx/error.log"),
PathBuf::from("/home/printnanny/.octoprint/logs"),
PathBuf::from("/var/log/klipper/"),
PathBuf::from("/var/log/moonraker/"),
PathBuf::from("/etc/issue"),
PathBuf::from("/etc/os-release"),
PathBuf::from("/var/log/syslog"),
]
}
}