Skip to main content

podbox/quadlet_install/
paths.rs

1//! Install-path helpers for Quadlet units: where `.container` files live,
2//! how installed units are discovered, and the systemd user-unit dir.
3//!
4//! Extracted verbatim from `quadlet_install.rs`.
5
6use std::path::PathBuf;
7
8use crate::config;
9
10/// Directory for user Quadlet source files.
11pub(crate) fn quadlet_dir() -> PathBuf {
12    dirs::config_dir()
13        .unwrap_or_else(|| config::expand_tilde("~/.config"))
14        .join("containers/systemd")
15}
16
17/// Flat install path: `~/.config/containers/systemd/<name>.container`.
18pub(crate) fn flat_container_path(name: &str) -> PathBuf {
19    quadlet_dir().join(format!("{name}.container"))
20}
21
22/// Application-scoped install path (Podman 6 directory/`--application` layout):
23/// `~/.config/containers/systemd/<name>/<name>.container`.
24pub(crate) fn application_container_path(name: &str) -> PathBuf {
25    quadlet_dir().join(name).join(format!("{name}.container"))
26}
27
28/// True if a `.container` Quadlet exists in either flat or application layout.
29pub fn is_installed(name: &str) -> bool {
30    container_unit_path(name).is_some()
31}
32
33/// Path to the installed `.container` unit, if any (flat preferred, then app dir).
34pub(crate) fn container_unit_path(name: &str) -> Option<PathBuf> {
35    let flat = flat_container_path(name);
36    if flat.exists() {
37        return Some(flat);
38    }
39    let app = application_container_path(name);
40    if app.exists() {
41        return Some(app);
42    }
43    None
44}
45
46/// Names of installed `.container` units under the Quadlet dir (flat + one app level).
47pub fn list_installed_names() -> Vec<String> {
48    let qdir = quadlet_dir();
49    let mut names = Vec::new();
50
51    let Ok(entries) = std::fs::read_dir(&qdir) else {
52        return names;
53    };
54
55    for entry in entries.flatten() {
56        let path = entry.path();
57        if path.extension().is_some_and(|e| e == "container") {
58            if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
59                names.push(stem.to_string());
60            }
61            continue;
62        }
63        // Application subdir: <name>/<name>.container
64        if path.is_dir() {
65            let Some(dir_name) = path.file_name().and_then(|s| s.to_str()) else {
66                continue;
67            };
68            let nested = path.join(format!("{dir_name}.container"));
69            if nested.exists() {
70                names.push(dir_name.to_string());
71            }
72        }
73    }
74
75    names.sort();
76    names.dedup();
77    names
78}
79
80/// Directory for user systemd unit files.
81pub(crate) fn systemd_user_dir() -> PathBuf {
82    dirs::config_dir()
83        .unwrap_or_else(|| config::expand_tilde("~/.config"))
84        .join("systemd/user")
85}