use std::collections::HashSet;
pub use isoloom_plan::images::rewrite_image;
use isoloom_spec::model::{Constraints, Mirrors, Spec};
use isoloom_spec::shell::quote;
use isoloom_spec::validate::docker_cidrs;
#[must_use]
pub fn of(spec: &Spec) -> Option<&Constraints> {
spec.constraints
.as_ref()
.filter(|c| c.proxy.is_some() || !c.registries.is_empty() || c.mirrors.is_some() || !c.ntp.is_empty())
}
fn proxies(c: &Constraints) -> Option<(&str, &str)> {
let p = c.proxy.as_ref()?;
let http = p.http.as_deref()?;
Some((http, p.https.as_deref().unwrap_or(http)))
}
#[must_use]
pub fn no_proxy(spec: &Spec) -> Vec<String> {
let local = ["localhost", "127.0.0.1", "::1"].map(str::to_owned);
let names = spec.machines.iter().flat_map(|(name, m)| std::iter::once(name).chain(&m.aliases).cloned());
let blocks = spec.networks.values().map(|n| n.cidr.clone());
let docker = docker_cidrs(spec).into_iter().map(|(_, c)| c.to_string());
let own = spec.constraints.iter().flat_map(|c| &c.proxy).flat_map(|p| p.no_proxy.iter().cloned());
let mut seen = HashSet::new();
local
.into_iter()
.chain(names)
.chain(blocks)
.chain(docker)
.chain(own)
.filter(|x| seen.insert(x.clone()))
.collect()
}
fn proxy_env(spec: &Spec, (http, https): (&str, &str)) -> Vec<(String, String)> {
let np = no_proxy(spec).join(",");
[
("HTTP_PROXY", "http_proxy", http),
("HTTPS_PROXY", "https_proxy", https),
("NO_PROXY", "no_proxy", np.as_str()),
]
.into_iter()
.flat_map(|(upper, lower, value)| [(upper.to_owned(), value.to_owned()), (lower.to_owned(), value.to_owned())])
.collect()
}
#[must_use]
pub fn machine_env(spec: &Spec) -> Vec<(String, String)> {
let Some(c) = of(spec) else { return Vec::new() };
let proxy = proxies(c).map(|urls| proxy_env(spec, urls)).unwrap_or_default();
let mirrors = c.mirrors.iter().flat_map(|m| {
[("PIP_INDEX_URL", &m.pypi), ("NPM_CONFIG_REGISTRY", &m.npm)]
.into_iter()
.filter_map(|(k, url)| Some((k.to_owned(), url.clone()?)))
});
proxy.into_iter().chain(mirrors).collect()
}
#[must_use]
pub fn build_args(spec: &Spec) -> Vec<(String, String)> {
machine_env(spec)
}
#[must_use]
pub fn linux_prelude(spec: &Spec) -> Option<String> {
let c = of(spec)?;
let mirrors = c.mirrors.as_ref().map(mirror_lines).unwrap_or_default();
Some(format!("set -e\n{}{mirrors}{}", proxy(spec, c), ntp(&c.ntp)))
}
fn mirror_lines(m: &Mirrors) -> String {
let sources = "/etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources";
let debian = m.debian.as_deref().map(|u| {
format!(
"# Debian's packages from the mirror.\n. /etc/os-release; if [ \"$ID\" = debian ]; then for f in {sources}; do if [ -f \"$f\" ]; then sed -i -E 's#https?://(deb|httpredir|ftp)\\.debian\\.org/debian/?([[:space:]]|$)#{}/\\2#g' \"$f\"; fi; done; fi\n",
u.trim_end_matches('/')
)
});
let ubuntu = m.ubuntu.as_deref().map(|u| {
format!(
"# Ubuntu's packages from the mirror.\n. /etc/os-release; if [ \"$ID\" = ubuntu ]; then for f in {sources}; do if [ -f \"$f\" ]; then sed -i -E 's#https?://([a-z0-9-]+\\.)?(archive|security)\\.ubuntu\\.com/ubuntu/?([[:space:]]|$)#{}/\\3#g' \"$f\"; fi; done; fi\n",
u.trim_end_matches('/')
)
});
let pypi = m
.pypi
.as_deref()
.map(|u| format!("printf '[global]\\nindex-url = %s\\n' {} > /etc/pip.conf\n", quote(u)));
let npm = m.npm.as_deref().map(|u| format!("printf 'registry=%s\\n' {} > /etc/npmrc\n", quote(u)));
[debian, ubuntu, pypi, npm].into_iter().flatten().collect()
}
fn ntp(servers: &[String]) -> String {
if servers.is_empty() {
return String::new();
}
format!(
"# Time from the site's servers: chrony when it's there, else systemd-timesyncd.\n\
if [ -d /etc/chrony ]; then mkdir -p /etc/chrony/conf.d /etc/chrony/sources.d; printf '%s\\n' {} > /etc/chrony/sources.d/isoloom.sources; systemctl restart chrony 2>/dev/null || systemctl restart chronyd 2>/dev/null || true; \
else mkdir -p /etc/systemd/timesyncd.conf.d && printf '[Time]\\nNTP=%s\\n' {} > /etc/systemd/timesyncd.conf.d/isoloom.conf && (systemctl restart systemd-timesyncd 2>/dev/null || true); fi\n",
servers.iter().map(|s| quote(&format!("server {s} iburst"))).collect::<Vec<_>>().join(" "),
quote(&servers.join(" ")),
)
}
fn upper(k: &str) -> bool {
k.chars().all(|ch| ch.is_ascii_uppercase() || ch == '_')
}
fn proxy(spec: &Spec, c: &Constraints) -> String {
let Some(urls) = proxies(c) else { return String::new() };
let env = proxy_env(spec, urls);
let lines: String = env.iter().map(|(k, v)| format!("{k}={v}\n")).collect::<Vec<_>>().concat();
let exports: String = env.iter().map(|(k, v)| format!("export {k}={}\n", quote(v))).collect::<Vec<_>>().concat();
let systemd = env
.iter()
.filter(|(k, _)| upper(k))
.map(|(k, v)| format!("\"{k}={v}\""))
.collect::<Vec<_>>()
.join(" ");
format!(
"# The proxy, for every login shell, systemd's services and sudo.\n\
grep -v -i -E '^(http|https|no)_proxy=' /etc/environment > /etc/environment.isoloom 2>/dev/null || true\n\
printf %s {} >> /etc/environment.isoloom && mv /etc/environment.isoloom /etc/environment\n\
printf %s {} > /etc/profile.d/isoloom-proxy.sh\n\
mkdir -p /etc/systemd/system.conf.d && printf '[Manager]\\nDefaultEnvironment=%s\\n' {} > /etc/systemd/system.conf.d/isoloom-proxy.conf\n\
if [ -d /etc/sudoers.d ]; then printf 'Defaults env_keep += \"http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY\"\\n' > /etc/sudoers.d/isoloom-proxy && chmod 0440 /etc/sudoers.d/isoloom-proxy; fi\n{}",
quote(&lines),
quote(&exports),
quote(&systemd),
package_proxies(urls, &env),
)
}
fn package_proxies((http, https): (&str, &str), env: &[(String, String)]) -> String {
let apt = format!("Acquire::http::Proxy \"{http}\";\nAcquire::https::Proxy \"{https}\";\n");
let docker: String = env
.iter()
.filter(|(k, _)| upper(k))
.map(|(k, v)| format!("Environment=\"{k}={v}\"\n"))
.collect::<Vec<_>>()
.concat();
format!(
"# apt, dnf/yum and Docker's daemon through it.\n\
if [ -d /etc/apt/apt.conf.d ]; then printf %s {} > /etc/apt/apt.conf.d/95isoloom-proxy; fi\n\
for f in /etc/dnf/dnf.conf /etc/yum.conf; do if [ -f \"$f\" ]; then sed -i '/^proxy=/d' \"$f\" && printf 'proxy=%s\\n' {} >> \"$f\"; fi; done\n\
mkdir -p /etc/systemd/system/docker.service.d && printf '[Service]\\n%s' {} > /etc/systemd/system/docker.service.d/isoloom-proxy.conf\n\
systemctl daemon-reload 2>/dev/null || true; if systemctl is-active --quiet docker 2>/dev/null; then systemctl restart docker; fi\n",
quote(&apt),
quote(http),
quote(&docker),
)
}
#[cfg(test)]
mod tests;