1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#![doc = include_str!("../README.md")]
pub mod config;
pub mod extension;
pub mod host;
pub mod port;

pub use config::read_and_resolve;

/// CLI argument parser.
#[cfg(feature = "bin")]
pub fn command() -> clap::Command {
    use clap::Arg;
    let c = clap::command!();
    c.long_about(
        "Mölla runns the Kvarn web server using plain-text configs.\n\
        See https://kvarn.org/moella/ for more details and how to write the config.\n\
        \n\
        Logging is controlled using the environment variable `KVARN_LOG`.\n\
        See https://docs.rs/env_logger/latest/env_logger/#example for log settings.",
    )
    .arg(
        Arg::new("config")
            .short('c')
            .long("config")
            .num_args(1)
            .required(true)
            .help("Main config file"),
    )
    .arg(
        Arg::new("high_ports")
            .long("high-ports")
            .help("Bind to higher ports (8080, 8443) to avoid permission issues")
            .action(clap::ArgAction::SetTrue),
    )
    .arg(
        Arg::new("dev")
            .long("dev")
            .help("Enable development environment. Uses high ports & disables all caches.")
            .conflicts_with("high_ports")
            .action(clap::ArgAction::SetTrue),
    )
    .arg(
        Arg::new("host")
            .short('h')
            .long("host")
            .help("Set the default host to show")
            .num_args(1),
    )
    .arg(
        Arg::new("instance")
            .long("instance-path")
            .long("ctl-socket")
            .short('p')
            .help(
                "The path of the control socket. \
                If you want to start multiple instances of moella, \
                consider this to be an instance name, \
                and make it different for all instances. \
                If you are using kvarnctl, remember to specify this \
                when running the kvarnctl, else it won't find this instance!",
            )
            .default_value("kvarn.sock"),
    )
    .disable_help_flag(true)
    .arg(
        Arg::new("help")
            .long("help")
            .action(clap::ArgAction::Help)
            .help("Print help"),
    )
}

/// Sets up logging, starts the server, and returns the handle.
/// Also handles argument parsing.
///
/// Logging is controlled using the environment variable `KVARN_LOG`.
/// See [this page](https://docs.rs/env_logger/latest/env_logger/#example) for log settings.
#[cfg(feature = "bin")]
pub async fn run(
    custom_extensions: &config::CustomExtensions,
) -> std::sync::Arc<kvarn::shutdown::Manager> {
    use self::config::CliOptions;

    let env_log = env_logger::Env::new().filter_or("KVARN_LOG", "rustls=off,info");
    env_logger::Builder::from_env(env_log).init();

    let matches = command().get_matches();

    let opts = CliOptions {
        high_ports: matches.get_flag("high_ports") || matches.get_flag("dev"),
        cache: !matches.get_flag("dev"),
        default_host: matches.get_one::<String>("host").map(String::as_str),
    };

    let mut rc = match config::read_and_resolve(
        matches.get_one::<String>("config").expect("it's required"),
        custom_extensions,
        &opts,
    )
    .await
    {
        Ok(rc) => rc,
        Err(s) => {
            log::error!("{s}");
            std::process::exit(1);
        }
    };

    let ctl_path = socket_path().join(
        matches
            .get_one::<String>("instance")
            .expect("we provided a default"),
    );
    rc = rc.set_ctl_path(ctl_path);

    rc.execute().await
}

#[cfg(feature = "bin")]
#[allow(unused_assignments)]
#[cfg_attr(windows, allow(unused_mut))]
pub(crate) fn socket_path() -> std::path::PathBuf {
    use std::path::Path;
    let mut p = Path::new("/run").to_path_buf();
    #[cfg(all(unix, target_os = "macos"))]
    {
        p = std::env::var_os("HOME")
            .map_or_else(|| Path::new("/Library/Caches").to_path_buf(), Into::into);
    }
    #[cfg(all(unix, not(target_os = "macos")))]
    {
        let user: u32 = unsafe { libc::getuid() };
        if user != 0 {
            p.push("user");
            p.push(user.to_string());
        }
    }
    p
}