From 513056f7118da82c8011710b65c569080e1fc2ca Mon Sep 17 00:00:00 2001
From: Jeremy Lin <jeremy.lin@gmail.com>
Date: Fri, 26 Feb 2021 19:40:12 -0800
Subject: [PATCH] Check for data folder on startup

Currently, when starting up for the first time (running standalone, outside
of Docker), bitwarden_rs panics when the `openssl` tool isn't able to create
`data/rsa_key.pem` due to the `data` dir not existing. Instead, print a more
helpful error message telling the user to create the directory.
---
 src/api/admin.rs |  4 ++--
 src/main.rs      | 25 +++++++++++++++++++++++--
 src/util.rs      |  9 +++++++++
 3 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/src/api/admin.rs b/src/api/admin.rs
index dacc1b7c..39fbc691 100644
--- a/src/api/admin.rs
+++ b/src/api/admin.rs
@@ -19,7 +19,7 @@ use crate::{
     db::{backup_database, models::*, DbConn, DbConnType},
     error::{Error, MapResult},
     mail,
-    util::{format_naive_datetime_local, get_display_size},
+    util::{format_naive_datetime_local, get_display_size, is_running_in_docker},
     CONFIG,
 };
 
@@ -486,7 +486,7 @@ fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
     let web_vault_version: WebVaultVersion = serde_json::from_str(&vault_version_str)?;
 
     // Execute some environment checks
-    let running_within_docker = std::path::Path::new("/.dockerenv").exists() || std::path::Path::new("/run/.containerenv").exists();
+    let running_within_docker = is_running_in_docker();
     let has_http_access = has_http_access();
     let uses_proxy = env::var_os("HTTP_PROXY").is_some()
         || env::var_os("http_proxy").is_some()
diff --git a/src/main.rs b/src/main.rs
index 6263cca0..d792dba5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -38,6 +38,7 @@ mod util;
 
 pub use config::CONFIG;
 pub use error::{Error, MapResult};
+pub use util::is_running_in_docker;
 
 fn main() {
     parse_args();
@@ -52,6 +53,7 @@ fn main() {
         _ => false,
     };
 
+    check_data_folder();
     check_rsa_keys();
     check_web_vault();
 
@@ -215,9 +217,28 @@ fn chain_syslog(logger: fern::Dispatch) -> fern::Dispatch {
     }
 }
 
+fn create_dir(path: &str, description: &str) {
+    // Try to create the specified dir, if it doesn't already exist.
+    let err_msg = format!("Error creating {} directory '{}'", description, path);
+    create_dir_all(path).expect(&err_msg);
+}
+
 fn create_icon_cache_folder() {
-    // Try to create the icon cache folder, and generate an error if it could not.
-    create_dir_all(&CONFIG.icon_cache_folder()).expect("Error creating icon cache directory");
+    create_dir(&CONFIG.icon_cache_folder(), "icon cache");
+}
+
+fn check_data_folder() {
+    let data_folder = &CONFIG.data_folder();
+    let path = Path::new(data_folder);
+    if !path.exists() {
+        error!("Data folder '{}' doesn't exist.", data_folder);
+        if is_running_in_docker() {
+            error!("Verify that your data volume is mounted at the correct location.");
+        } else {
+            error!("Create the data folder and try again.");
+        }
+        exit(1);
+    }
 }
 
 fn check_rsa_keys() {
diff --git a/src/util.rs b/src/util.rs
index e025237c..82343bcf 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -358,6 +358,15 @@ pub fn format_naive_datetime_local(dt: &NaiveDateTime, fmt: &str) -> String {
     format_datetime_local(&Local.from_utc_datetime(dt), fmt)
 }
 
+//
+// Deployment environment methods
+//
+
+/// Returns true if the program is running in Docker or Podman.
+pub fn is_running_in_docker() -> bool {
+    Path::new("/.dockerenv").exists() || Path::new("/run/.containerenv").exists()
+}
+
 //
 // Deserialization methods
 //