use std::collections::HashMap;
use std::default::Default;
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use async_trait::async_trait;
use figment::providers::Env;
use log::{debug, info};
use serde::{Deserialize, Serialize};
use printnanny_dbus::zbus;
use printnanny_dbus::zbus_systemd;
use crate::error::VersionControlledSettingsError;
use crate::printnanny::GitSettings;
use crate::vcs::{VersionControlledSettings, DEFAULT_VCS_SETTINGS_DIR};
use crate::SettingsFormat;
pub const MOONRAKER_INSTALL_DIR: &str = "/home/printnanny/.moonraker";
pub const MOONRAKER_VENV: &str = "/home/printnanny/moonraker-venv";
pub const DEFAULT_MOONRAKER_SETTINGS_FILE: &str = "moonraker/moonraker.conf";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerServerSettings {
pub host: IpAddr,
pub port: u16,
pub ssl_port: u16,
pub klippy_uds_address: PathBuf,
pub max_upload_size: u32,
}
impl Default for MoonrakerServerSettings {
fn default() -> Self {
Self {
host: "0.0.0.0".parse().unwrap(),
port: 7125,
ssl_port: 7130,
klippy_uds_address: PathBuf::from("/var/run/klippy/klippy.sock"),
max_upload_size: 1024,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerFileManagerSettings {
pub queue_gcode_uploads: bool,
pub enable_object_processing: bool,
pub enable_inotify_warnings: bool,
}
impl Default for MoonrakerFileManagerSettings {
fn default() -> Self {
Self {
queue_gcode_uploads: false,
enable_object_processing: false,
enable_inotify_warnings: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum MoonrakerSystemServiceProvider {
#[serde(rename = "systemd_debus")]
SystemdDbus,
#[serde(rename = "systemd_cli")]
SystemdCli,
#[serde(rename = "none")]
None,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerMachineSettings {
pub provider: MoonrakerSystemServiceProvider,
pub validate_service: bool,
pub validate_config: bool,
pub force_validation: bool,
}
impl Default for MoonrakerMachineSettings {
fn default() -> Self {
Self {
provider: MoonrakerSystemServiceProvider::SystemdDbus,
validate_service: false,
validate_config: true,
force_validation: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerDataStoreSettings {
pub temperature_store_size: u32,
pub gcode_store_size: u32,
}
impl Default for MoonrakerDataStoreSettings {
fn default() -> Self {
Self {
temperature_store_size: 1200, gcode_store_size: 1000, }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct MoonrakerJobQueueSettings {
pub load_on_startup: bool,
pub automatic_transition: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub job_transition_delay: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub job_transition_gcode: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct MoonrakerAnnouncementSettings {
pub subscriptions: Vec<String>,
pub dev_mode: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerWebcamSettings {
pub location: String,
pub service: String,
pub target_fps: u32,
pub stream_url: String,
pub snapshot_url: String,
pub flip_horizontal: bool,
pub flip_vertical: bool,
pub rotation: u32,
}
impl Default for MoonrakerWebcamSettings {
fn default() -> Self {
Self {
location: "printnanny".into(),
service: "printnanny-vision.service".into(),
target_fps: 15,
stream_url: "/printnanny-hls/playlist.m3u8".into(),
snapshot_url: "/printnanny-hls/playlist.m3u8".into(),
flip_horizontal: false,
flip_vertical: false,
rotation: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum MoonrakerAuthorizationSource {
#[serde(rename = "moonraker")]
Moonraker,
#[serde(rename = "ldap")]
Ldap,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerAuthorizationSettings {
pub login_timeout: u32,
pub trusted_clients: Vec<String>,
pub cors_domains: Vec<String>,
pub force_logins: bool,
pub default_source: MoonrakerAuthorizationSource,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerLDAP {
pub ldap_host: String,
pub ldap_port: u16,
pub base_dn: String,
pub bind_dn: String,
pub bind_password: String,
pub group_dn: String,
pub is_active_directory: bool,
pub user_filter: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerOctoPrintCompat {
pub enable_ufp: bool,
pub flip_h: bool,
pub flip_v: bool,
pub rotate_90: bool,
pub stream_url: String,
pub webcam_enabled: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerMqttSettings {
pub address: String,
pub port: u16,
pub username: String,
pub password: String,
pub mqtt_protocol: String,
pub enable_moonraker_api: bool,
pub instance_name: String,
pub status_objects: Vec<String>,
pub default_qos: u8,
pub api_qos: u8,
}
impl Default for MoonrakerMqttSettings {
fn default() -> Self {
let hostname = sys_info::hostname().unwrap_or_else(|_| "localhost".to_string());
Self {
address: "mqtt.live.printnanny.ai".into(),
port: 1883,
username: "{secrets.mqtt_credentials.username}".into(), password: "{secrets.mqtt_credentials.password}".into(), mqtt_protocol: "v3.1.1".into(),
enable_moonraker_api: true,
instance_name: hostname,
status_objects: vec![],
default_qos: 0,
api_qos: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerMqttCredentials {
username: String,
password: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerSecretsSettings {
pub mqtt_credentials: MoonrakerMqttCredentials,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerCfg {
#[serde(skip_serializing_if = "Option::is_none")]
pub authorization: Option<MoonrakerAuthorizationSource>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ldap: Option<MoonrakerLDAP>,
pub secrets: Option<MoonrakerSecretsSettings>,
pub octoprint_compat: Option<MoonrakerOctoPrintCompat>,
pub announcements: MoonrakerAnnouncementSettings,
pub data_store: MoonrakerDataStoreSettings,
pub job_queue: MoonrakerJobQueueSettings,
pub file_manager: MoonrakerFileManagerSettings,
pub machine: MoonrakerMachineSettings,
pub server: MoonrakerServerSettings,
pub mqtt: MoonrakerMqttSettings,
pub webcam: HashMap<String, MoonrakerWebcamSettings>,
}
impl Default for MoonrakerCfg {
fn default() -> Self {
let mut webcam = HashMap::new();
webcam.insert("printnanny".into(), MoonrakerWebcamSettings::default());
Self {
authorization: None,
ldap: None,
octoprint_compat: None,
secrets: None,
announcements: MoonrakerAnnouncementSettings::default(),
data_store: MoonrakerDataStoreSettings::default(),
job_queue: MoonrakerJobQueueSettings::default(),
file_manager: MoonrakerFileManagerSettings::default(),
machine: MoonrakerMachineSettings::default(),
server: MoonrakerServerSettings::default(),
mqtt: MoonrakerMqttSettings::default(),
webcam,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MoonrakerSettings {
pub enabled: bool,
pub install_dir: PathBuf,
pub settings_file: PathBuf,
pub settings_format: SettingsFormat,
pub venv: PathBuf,
pub git_settings: GitSettings,
}
impl Default for MoonrakerSettings {
fn default() -> Self {
let install_dir: PathBuf = MOONRAKER_INSTALL_DIR.into();
let default_settings_file =
PathBuf::from(DEFAULT_VCS_SETTINGS_DIR).join(DEFAULT_MOONRAKER_SETTINGS_FILE);
let settings_file = PathBuf::from(Env::var_or(
"MOONRAKER_SETTINGS_FILE",
default_settings_file.display().to_string(),
));
let git_settings = GitSettings::default();
Self {
settings_file,
install_dir,
enabled: false,
venv: MOONRAKER_VENV.into(),
settings_format: SettingsFormat::Ini,
git_settings,
}
}
}
#[async_trait]
impl VersionControlledSettings for MoonrakerSettings {
type SettingsModel = MoonrakerSettings;
fn from_dir(settings_dir: &Path) -> Self {
let settings_file = settings_dir.join("moonraker/moonraker.conf");
Self {
settings_file,
..Self::default()
}
}
fn get_settings_format(&self) -> SettingsFormat {
self.settings_format
}
fn get_settings_file(&self) -> PathBuf {
self.settings_file.clone()
}
fn get_git_repo_path(&self) -> &Path {
&self.git_settings.path
}
fn get_git_remote(&self) -> &str {
&self.git_settings.remote
}
fn get_git_settings(&self) -> &GitSettings {
&self.git_settings
}
async fn pre_save(&self) -> Result<(), VersionControlledSettingsError> {
debug!("Running KlipperSettings pre_save hook");
let connection = zbus::Connection::system().await?;
let proxy = zbus_systemd::systemd1::ManagerProxy::new(&connection).await?;
let job = proxy
.stop_unit("klipper.service".to_string(), "replace".to_string())
.await?;
info!("Stopped klipper.service, job: {:?}", job);
Ok(())
}
async fn post_save(&self) -> Result<(), VersionControlledSettingsError> {
debug!("Running KlipperSettings post_save hook");
let connection = zbus::Connection::system().await?;
let proxy = zbus_systemd::systemd1::ManagerProxy::new(&connection).await?;
let job = proxy
.start_unit("klipper.service".into(), "replace".into())
.await?;
info!("Started klipper.service, job: {:?}", job);
Ok(())
}
fn validate(&self) -> Result<(), VersionControlledSettingsError> {
todo!("OctoPrintSettings validate hook is not yet implemented");
}
}