#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Family {
Apt,
Dnf,
Yum,
Apk,
}
impl Family {
pub(super) fn of(os_family: &str) -> Family {
match os_family {
"rocky" | "alma" | "fedora" => Family::Dnf,
"centos" => Family::Yum,
"alpine" => Family::Apk,
_ => Family::Apt,
}
}
}
pub(super) struct Package {
pub(super) port: u16,
apt: &'static str,
red_hat: &'static str,
apk: &'static str,
unit_apt: &'static str,
unit_red_hat: &'static str,
pub(super) configure: &'static str,
}
impl Package {
pub(super) fn name(&self, family: Family) -> &'static str {
match family {
Family::Apt => self.apt,
Family::Dnf | Family::Yum => self.red_hat,
Family::Apk => self.apk,
}
}
pub(super) fn unit(&self, family: Family) -> &'static str {
match family {
Family::Apt => self.unit_apt,
Family::Dnf | Family::Yum | Family::Apk => self.unit_red_hat,
}
}
}
const POSTGRES_CONFIGURE: &str = r#"# Red Hat family: create the database cluster first (Debian's package does it itself).
if command -v postgresql-setup >/dev/null 2>&1 && [ ! -f /var/lib/pgsql/data/PG_VERSION ]; then
postgresql-setup --initdb || runuser -u postgres -- initdb -D /var/lib/pgsql/data
fi
conf=$(find /etc/postgresql /var/lib/pgsql -name postgresql.conf 2>/dev/null | head -1 || true)
hba=$(find /etc/postgresql /var/lib/pgsql -name pg_hba.conf 2>/dev/null | head -1 || true)
if [ -n "$conf" ]; then sed -i "s/^#\?listen_addresses.*/listen_addresses = '*'/" "$conf"; fi
if [ -n "$hba" ] && ! grep -q "0.0.0.0/0" "$hba"; then echo "host all all 0.0.0.0/0 scram-sha-256" >> "$hba"; fi"#;
const MARIADB_CONFIGURE: &str = r#"for f in /etc/mysql/mariadb.conf.d/50-server.cnf /etc/my.cnf.d/mariadb-server.cnf; do
if [ -f "$f" ]; then sed -i "s/^bind-address.*/bind-address = 0.0.0.0/" "$f"; fi
done"#;
const REDIS_CONFIGURE: &str = r#"for f in /etc/redis/redis.conf /etc/redis.conf; do
if [ -f "$f" ]; then sed -i -e "s/^bind .*/bind 0.0.0.0/" -e "s/^protected-mode yes/protected-mode no/" "$f"; fi
done"#;
const fn plain(port: u16, apt: &'static str, red_hat: &'static str, apk: &'static str, units: (&'static str, &'static str)) -> Package {
Package {
port,
apt,
red_hat,
apk,
unit_apt: units.0,
unit_red_hat: units.1,
configure: "",
}
}
pub(super) const PACKAGES: &[Package] = &[
plain(22, "openssh-server", "openssh-server", "openssh", ("ssh", "sshd")),
plain(80, "nginx", "nginx", "nginx", ("nginx", "nginx")),
plain(443, "nginx", "nginx", "nginx", ("nginx", "nginx")),
Package {
configure: POSTGRES_CONFIGURE,
..plain(5432, "postgresql", "postgresql-server", "postgresql16", ("postgresql", "postgresql"))
},
Package {
configure: MARIADB_CONFIGURE,
..plain(3306, "mariadb-server", "mariadb-server", "mariadb", ("mariadb", "mariadb"))
},
Package {
configure: REDIS_CONFIGURE,
..plain(6379, "redis-server", "redis", "redis", ("redis-server", "redis"))
},
plain(53, "bind9", "bind", "bind", ("named", "named")),
plain(21, "vsftpd", "vsftpd", "vsftpd", ("vsftpd", "vsftpd")),
plain(25, "postfix", "postfix", "postfix", ("postfix", "postfix")),
];
pub(super) fn wanted(ports: &[u16], family: Family) -> Vec<&'static Package> {
let mut wanted: Vec<&Package> = Vec::new();
for p in PACKAGES.iter().filter(|p| ports.contains(&p.port)) {
if !wanted.iter().any(|w| w.name(family) == p.name(family)) {
wanted.push(p);
}
}
wanted
}
pub(super) fn install(wanted: &[&Package], family: Family) -> String {
let names: Vec<&str> = wanted.iter().map(|p| p.name(family)).collect();
let names = names.join(" ");
match family {
Family::Apt => format!("export DEBIAN_FRONTEND=noninteractive\napt-get update\napt-get install -y {names}\n"),
Family::Dnf => format!("dnf install -y {names}\n"),
Family::Yum => format!("yum install -y {names}\n"),
Family::Apk => format!("apk add --no-cache {names}\n"),
}
}
pub(super) fn start(wanted: &[&Package], family: Family) -> String {
let (guard, enable, restart) = match family {
Family::Apk => (
"command -v rc-service >/dev/null 2>&1 && [ -d /run/openrc ]",
"rc-update add {} default",
"rc-service {} restart",
),
_ => ("[ -d /run/systemd/system ]", "systemctl enable {}", "systemctl restart {}"),
};
let units: Vec<String> = wanted
.iter()
.map(|p| p.unit(family))
.map(|unit| format!(" {}\n {}\n", enable.replace("{}", unit), restart.replace("{}", unit)))
.collect();
let units = units.concat();
format!("# Start the services where an init system runs them (a VM; a container's build has none).\nif {guard}; then\n{units}fi\n")
}