nimbus_cli/
config.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use std::path::PathBuf;
6
7use crate::{
8    cli::Cli,
9    version_utils::{is_before, pad_major, pad_major_minor, pad_major_minor_patch},
10    LaunchableApp, NimbusApp,
11};
12use anyhow::{bail, Result};
13
14impl TryFrom<&Cli> for LaunchableApp {
15    type Error = anyhow::Error;
16    fn try_from(value: &Cli) -> Result<Self> {
17        Self::try_from_app_channel_device(
18            value.app.as_deref(),
19            value.channel.as_deref(),
20            value.device_id.as_deref(),
21        )
22    }
23}
24
25impl LaunchableApp {
26    pub(crate) fn try_from_app_channel_device(
27        app: Option<&str>,
28        channel: Option<&str>,
29        device_id: Option<&str>,
30    ) -> Result<Self> {
31        match (&app, &channel) {
32            (None, None) => anyhow::bail!("A value for --app and --channel must be specified. Supported apps are: fenix, focus_android, firefox_ios and focus_ios"),
33            (None, _) => anyhow::bail!("A value for --app must be specified. One of: fenix, focus_android, firefox_ios and focus_ios are currently supported"),
34            (_, None) => anyhow::bail!("A value for --channel must be specified. Supported channels are: developer, nightly, beta and release"),
35            _ => (),
36        }
37
38        let app = app.unwrap();
39        let channel = channel.unwrap();
40
41        let prefix = match app {
42            "fenix" => Some("org.mozilla"),
43            "focus_android" => Some("org.mozilla"),
44            "firefox_ios" => Some("org.mozilla.ios"),
45            "focus_ios" => Some("org.mozilla.ios"),
46            _ => anyhow::bail!("Only --app values of fenix, focus_android, firefox_ios and focus_ios are currently supported"),
47        };
48
49        let suffix = match app {
50            "fenix" => Some(match channel {
51                "developer" => "fenix.debug",
52                "nightly" => "fenix",
53                "beta" => "firefox_beta",
54                "release" => "firefox",
55                _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, nightly, beta or release", app, channel)),
56            }),
57            "focus_android" => Some(match channel {
58                "developer" => "focus.debug",
59                "nightly" => "focus.nightly",
60                "beta" => "focus.beta",
61                "release" => "focus",
62                _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, nightly, beta or release", app, channel)),
63            }),
64            "firefox_ios" => Some(match channel {
65                "developer" => "Fennec",
66                "beta" => "FirefoxBeta",
67                "release" => "Firefox",
68                _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, beta or release", app, channel)),
69            }),
70            "focus_ios" => Some(match channel {
71                "developer" => "Focus",
72                "beta" => "Focus",
73                "release" => "Focus",
74                _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, beta or release", app, channel)),
75            }),
76            _ => None,
77        };
78
79        // Scheme for deeplinks.
80        let scheme = match app {
81            "fenix" => Some(match channel {
82                // Firefox for Android defines per channel deeplink schemes in the app/build.gradle.
83                // e.g. https://github.com/mozilla-mobile/firefox-android/blob/5d18e7ffe2f3e4505ea815d584d20e66ad10f515/fenix/app/build.gradle#L154
84                "developer" => "fenix-dev",
85                "nightly" => "fenix-nightly",
86                "beta" => "fenix-beta",
87                "release" => "fenix",
88                _ => unreachable!(),
89            }),
90            "firefox_ios" => Some(match channel {
91                // Firefox for iOS uses MOZ_PUBLIC_URL_SCHEME, which is always
92                // [`firefox`](https://github.com/mozilla-mobile/firefox-ios/blob/f1acc8a2232a736e65e235b811372ddbf3e802f8/Client/Configuration/Common.xcconfig#L24)
93                // and MOZ_INTERNAL_URL_SCHEME which is different per channel.
94                // e.g. https://github.com/mozilla-mobile/firefox-ios/blob/f1acc8a2232a736e65e235b811372ddbf3e802f8/Client/Configuration/Firefox.xcconfig#L12
95                // From inspection of the code, there are no different uses for the internal vs
96                // public, so very useful for launching the specific app on a phone where you
97                // have multiple versions installed.
98                "developer" => "fennec",
99                "beta" => "firefox-beta",
100                "release" => "firefox-internal",
101                _ => unreachable!(),
102            }),
103            // Focus for iOS has two, firefox-focus and firefox-klar
104            // It's not clear if Focus's channels are configured for this
105            "focus_ios" => Some("firefox-focus"),
106
107            // Focus for Android provides no deeplinks.
108            _ => None,
109        }
110        .map(str::to_string);
111
112        Ok(match (app, prefix, suffix) {
113            ("fenix", Some(prefix), Some(suffix)) => Self::Android {
114                package_name: format!("{}.{}", prefix, suffix),
115                activity_name: ".App".to_string(),
116                device_id: device_id.map(str::to_string),
117                scheme,
118                open_deeplink: Some("open".to_string()),
119            },
120            ("focus_android", Some(prefix), Some(suffix)) => Self::Android {
121                package_name: format!("{}.{}", prefix, suffix),
122                activity_name: "org.mozilla.focus.activity.MainActivity".to_string(),
123                device_id: device_id.map(str::to_string),
124                scheme,
125                open_deeplink: None,
126            },
127            ("firefox_ios", Some(prefix), Some(suffix)) => Self::Ios {
128                app_id: format!("{}.{}", prefix, suffix),
129                device_id: device_id.unwrap_or("booted").to_string(),
130                scheme,
131            },
132            ("focus_ios", Some(prefix), Some(suffix)) => Self::Ios {
133                app_id: format!("{}.{}", prefix, suffix),
134                device_id: device_id.unwrap_or("booted").to_string(),
135                scheme,
136            },
137            _ => unreachable!(),
138        })
139    }
140}
141
142impl NimbusApp {
143    pub(crate) fn ref_from_version(
144        &self,
145        version: &Option<String>,
146        ref_: &String,
147    ) -> Result<String> {
148        let app_name = self
149            .app_name()
150            .ok_or_else(|| anyhow::anyhow!("Either an --app or a --manifest must be specified"))?;
151
152        if version.is_none() {
153            // gecko-dev uses master, not main
154            if (app_name == "fenix" || app_name == "focus_android") && ref_ == "main" {
155                return Ok("master".into());
156            }
157
158            return Ok(ref_.to_string());
159        }
160
161        let v = version.as_ref().unwrap();
162        let v = match app_name.as_str() {
163            "fenix" => {
164                if is_before(version, 111) {
165                    pad_major_minor_patch(v)
166                } else if is_before(version, 126) {
167                    pad_major(v)
168                } else {
169                    bail!("gecko-dev does not have tagged versions, use --ref instead")
170                }
171            }
172            "focus_android" => {
173                if is_before(version, 110) {
174                    pad_major_minor(v)
175                } else if is_before(version, 126) {
176                    pad_major(v)
177                } else {
178                    bail!("gecko-dev does not have tagged versions, use --ref instead")
179                }
180            }
181            "firefox_ios" => {
182                if is_before(version, 112) {
183                    pad_major_minor(v)
184                } else {
185                    pad_major(v)
186                }
187            }
188            "focus_ios" => pad_major(v),
189            _ => v.to_string(),
190        };
191
192        Ok(match app_name.as_str() {
193            "fenix" => format!("releases_v{v}"),
194            "focus_android" => format!("releases_v{v}"),
195            "firefox_ios" => {
196                if is_before(version, 106) {
197                    format!("v{v}")
198                } else {
199                    format!("release/v{v}")
200                }
201            }
202            "focus_ios" => format!("releases_v{v}"),
203
204            _ => anyhow::bail!("{} is not defined", app_name),
205        })
206    }
207
208    pub(crate) fn github_repo<'a>(&self, version: &Option<String>) -> Result<&'a str> {
209        let app_name = self
210            .app_name()
211            .ok_or_else(|| anyhow::anyhow!("Either an --app or a --manifest must be specified"))?;
212        Ok(match app_name.as_str() {
213            // Fenix and Focus are both in the same repo
214            "fenix" => {
215                if is_before(version, 111) {
216                    "mozilla-mobile/fenix"
217                } else if is_before(version, 126) {
218                    "mozilla-mobile/firefox-android"
219                } else {
220                    "mozilla/gecko-dev"
221                }
222            }
223            "focus_android" => {
224                if is_before(version, 110) {
225                    "mozilla-mobile/focus-android"
226                } else if is_before(version, 126) {
227                    "mozilla-mobile/firefox-android"
228                } else {
229                    "mozilla/gecko-dev"
230                }
231            }
232            "firefox_ios" => "mozilla-mobile/firefox-ios",
233            "focus_ios" => "mozilla-mobile/focus-ios",
234            _ => unreachable!("{} is not defined", app_name),
235        })
236    }
237
238    pub(crate) fn manifest_location<'a>(&self, version: &Option<String>) -> Result<&'a str> {
239        let app_name = self
240            .app_name()
241            .ok_or_else(|| anyhow::anyhow!("Either an --app or a --manifest must be specified"))?;
242        Ok(match app_name.as_str() {
243            "fenix" => {
244                if is_before(version, 98) {
245                    bail!("Fenix wasn't Nimbus enabled before v98")
246                } else if is_before(version, 111) {
247                    "nimbus.fml.yaml"
248                } else if is_before(version, 112) {
249                    "fenix/nimbus.fml.yaml"
250                } else if is_before(version, 126) {
251                    "fenix/app/nimbus.fml.yaml"
252                } else {
253                    "mobile/android/fenix/app/nimbus.fml.yaml"
254                }
255            }
256            "focus_android" => {
257                if is_before(version, 102) {
258                    bail!("Focus for Android wasn't Nimbus enabled before v102")
259                } else if is_before(version, 110) {
260                    "nimbus.fml.yaml"
261                } else if is_before(version, 112) {
262                    "focus-android/nimbus.fml.yaml"
263                } else if is_before(version, 126) {
264                    "focus-android/app/nimbus.fml.yaml"
265                } else {
266                    "mobile/android/focus-android/app/nimbus.fml.yaml"
267                }
268            }
269            "firefox_ios" => {
270                if is_before(version, 98) {
271                    bail!("Firefox for iOS wasn't Nimbus enabled before v98")
272                } else if is_before(version, 122) {
273                    "nimbus.fml.yaml"
274                } else {
275                    "firefox-ios/nimbus.fml.yaml"
276                }
277            }
278            "focus_ios" => {
279                if is_before(version, 108) {
280                    bail!("Focus wasn't Nimbus enabled before v108")
281                } else if is_before(version, 122) {
282                    "nimbus.fml.yaml"
283                } else {
284                    "focus-ios/nimbus.fml.yaml"
285                }
286            }
287            _ => anyhow::bail!("{} is not defined", app_name),
288        })
289    }
290}
291
292pub(crate) fn rs_production_server() -> String {
293    std::env::var("NIMBUS_URL")
294        .unwrap_or_else(|_| "https://firefox.settings.services.mozilla.com".to_string())
295}
296
297pub(crate) fn rs_stage_server() -> String {
298    std::env::var("NIMBUS_URL_STAGE")
299        .unwrap_or_else(|_| "https://firefox.settings.services.allizom.org".to_string())
300}
301
302pub(crate) fn api_v6_production_server() -> String {
303    std::env::var("NIMBUS_API_URL")
304        .unwrap_or_else(|_| "https://experimenter.services.mozilla.com".to_string())
305}
306
307pub(crate) fn api_v6_stage_server() -> String {
308    std::env::var("NIMBUS_API_URL_STAGE").unwrap_or_else(|_| {
309        "https://stage.experimenter.nonprod.webservices.mozgcp.net/api/v6".to_string()
310    })
311}
312
313pub(crate) fn manifest_cache_dir() -> Option<PathBuf> {
314    match std::env::var("NIMBUS_MANIFEST_CACHE") {
315        Ok(s) => {
316            let cwd = std::env::current_dir().expect("Current Working Directory is not set");
317            Some(cwd.join(s))
318        }
319        // We let the Nimbus FML define its own cache.
320        _ => None,
321    }
322}
323
324#[cfg(feature = "server")]
325pub(crate) fn server_port() -> String {
326    match std::env::var("NIMBUS_CLI_SERVER_PORT") {
327        Ok(s) => s,
328        _ => "8080".to_string(),
329    }
330}
331
332#[cfg(feature = "server")]
333pub(crate) fn server_host() -> String {
334    match std::env::var("NIMBUS_CLI_SERVER_HOST") {
335        Ok(s) => s,
336        _ => {
337            use local_ip_address::local_ip;
338            let ip = local_ip().unwrap();
339            ip.to_string()
340        }
341    }
342}