innisfree 0.4.3

Exposes local services on public IPv4 address, via cloud server.
Documentation
//! Render the systemd unit (`innisfree@.service`) from the in-tree
//! Tera template at `files/innisfree@.service`.
//!
//! Keeping the unit in code (compiled in via `include_str!`) gives us
//! one source of truth and lets callers render it pointing at any
//! binary path — `/usr/bin/innisfree` for packaging, the cargo-built
//! artifact for tests. Exposed via the `innisfree systemd-service`
//! subcommand so the deb pipeline can regenerate the unit at packaging
//! time. Reuses the `tera` crate that's already in the dep graph
//! (also used by `files/stream.conf.j2`), so no new dependencies.

use anyhow::{Context, Result};
use std::path::Path;
use tera::Tera;

/// In-tree systemd unit template (Tera). The single substitution is
/// `{{ executable_path }}`, which [`render_unit`] fills with the
/// caller-supplied path. Systemd's `%i` is left intact for runtime
/// expansion (Tera uses `{{ }}` / `{% %}`, so there's no conflict
/// with `%i`).
const UNIT_TEMPLATE: &str = include_str!("../files/innisfree@.service");

/// Render the systemd unit with `executable_path` baked into the
/// `ExecStart=` directive. The returned string is suitable for
/// dropping into `/usr/lib/systemd/system/innisfree@.service`.
pub fn render_unit(executable_path: &Path) -> Result<String> {
    let mut ctx = tera::Context::new();
    ctx.insert("executable_path", &executable_path.display().to_string());
    Tera::one_off(UNIT_TEMPLATE, &ctx, false).context("rendering systemd unit template")
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    #[test]
    fn renders_executable_path_into_exec_start() {
        let unit = render_unit(&PathBuf::from("/opt/bin/innisfree")).unwrap();
        assert!(
            unit.contains("ExecStart=/opt/bin/innisfree up --name %i"),
            "got:\n{unit}"
        );
    }

    #[test]
    fn rendered_unit_has_no_template_marker() {
        let unit = render_unit(&PathBuf::from("/usr/bin/innisfree")).unwrap();
        assert!(
            !unit.contains("{{") && !unit.contains("}}"),
            "Tera placeholder leaked into output:\n{unit}"
        );
    }

    #[test]
    fn preserves_systemd_instance_substitution() {
        // %i must be left intact so systemd can expand it per-instance.
        let unit = render_unit(&PathBuf::from("/usr/bin/innisfree")).unwrap();
        assert!(unit.contains("--name %i"), "got:\n{unit}");
        assert!(unit.contains("Description=innisfree tunnel for %i"));
    }

    #[test]
    fn declares_required_capabilities() {
        let unit = render_unit(&PathBuf::from("/usr/bin/innisfree")).unwrap();
        // Both directives must list the same caps; otherwise systemd
        // will silently fail to grant CAP_NET_ADMIN at runtime.
        assert!(unit.contains("AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE"));
        assert!(unit.contains("CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE"));
    }

    #[test]
    fn install_section_targets_multi_user() {
        let unit = render_unit(&PathBuf::from("/usr/bin/innisfree")).unwrap();
        assert!(unit.contains("[Install]\nWantedBy=multi-user.target"));
    }
}