use crate::brew::Brew;
use crate::daemon::ServiceSpec;
use crate::paths;
use crate::state::ServiceKind;
use anyhow::Result;
use std::net::{SocketAddr, TcpStream};
use std::time::Duration;
struct ServiceDef {
formula: &'static str,
tap: Option<&'static str>,
bin: &'static str,
port: u16,
blurb: &'static str,
}
fn def(kind: ServiceKind) -> ServiceDef {
match kind {
ServiceKind::Mysql => ServiceDef {
formula: "mysql",
tap: None,
bin: "bin/mysqld",
port: 3306,
blurb: "MySQL database",
},
ServiceKind::Mariadb => ServiceDef {
formula: "mariadb",
tap: None,
bin: "bin/mariadbd",
port: 3306,
blurb: "MariaDB database",
},
ServiceKind::Postgres => ServiceDef {
formula: "postgresql@16",
tap: None,
bin: "bin/postgres",
port: 5432,
blurb: "PostgreSQL 16 database",
},
ServiceKind::Redis => ServiceDef {
formula: "redis",
tap: None,
bin: "bin/redis-server",
port: 6379,
blurb: "Redis key-value store",
},
ServiceKind::Memcached => ServiceDef {
formula: "memcached",
tap: None,
bin: "bin/memcached",
port: 11211,
blurb: "memcached cache",
},
ServiceKind::Mailpit => ServiceDef {
formula: "mailpit",
tap: None,
bin: "bin/mailpit",
port: 8025,
blurb: "Mailpit mail catcher (SMTP :1025, UI :8025)",
},
}
}
fn args(kind: ServiceKind, brew: &Brew) -> Vec<String> {
let p = |sub: &str| brew.prefix.join(sub).display().to_string();
match kind {
ServiceKind::Mysql | ServiceKind::Mariadb => {
vec![format!("--datadir={}", p("var/mysql"))]
}
ServiceKind::Postgres => vec!["-D".into(), p("var/postgresql@16")],
ServiceKind::Redis => vec![p("etc/redis.conf"), "--daemonize".into(), "no".into()],
ServiceKind::Memcached => vec!["-p".into(), "11211".into()],
ServiceKind::Mailpit => vec![],
}
}
pub fn service_id(kind: ServiceKind) -> String {
format!("svc-{}", kind.as_str())
}
pub fn formula(kind: ServiceKind) -> &'static str {
def(kind).formula
}
pub fn port(kind: ServiceKind) -> u16 {
def(kind).port
}
pub fn blurb(kind: ServiceKind) -> &'static str {
def(kind).blurb
}
pub fn is_installed(brew: &Brew, kind: ServiceKind) -> bool {
brew.is_installed(def(kind).formula)
}
pub fn ensure_installed(brew: &Brew, kind: ServiceKind) -> Result<()> {
let d = def(kind);
if let Some(tap) = d.tap {
brew.ensure_tap(tap)?;
}
if !brew.is_installed(d.formula) {
println!("Installing {}…", d.formula);
brew.install(d.formula)?;
}
Ok(())
}
pub fn service_spec(brew: &Brew, kind: ServiceKind) -> Result<ServiceSpec> {
let d = def(kind);
Ok(ServiceSpec {
service: service_id(kind),
program: brew.opt(d.formula).join(d.bin),
args: args(kind, brew),
log: paths::logs_dir()?.join(format!("{}.log", service_id(kind))),
keep_alive: true,
run_at_load: true,
})
}
pub fn health(kind: ServiceKind) -> bool {
let addr = SocketAddr::from(([127, 0, 0, 1], def(kind).port));
TcpStream::connect_timeout(&addr, Duration::from_millis(300)).is_ok()
}