mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-04-09 11:01:39 -06:00
Panic on unrecognised DATABASE_URL instead of silent SQLite fallback
Previously, any DATABASE_URL that did not match the mysql: or postgresql: prefix was silently treated as a SQLite file path. This caused data loss in containerised environments when the URL was misconfigured (typos, quoting issues), as vaultwarden would create an ephemeral SQLite database that was wiped on restart. Now, an explicit sqlite:// prefix is supported and used as the default. Bare paths without a recognised scheme are still accepted for backwards compatibility, but only if the database file already exists. If not, the process panics with a clear error message. Relates to #2835, #1910, #860.
This commit is contained in:
parent
3f28b583db
commit
df56449823
@ -50,10 +50,11 @@
|
||||
#########################
|
||||
|
||||
## Database URL
|
||||
## When using SQLite, this is the path to the DB file, and it defaults to
|
||||
## %DATA_FOLDER%/db.sqlite3. If DATA_FOLDER is set to an external location, this
|
||||
## must be set to a local sqlite3 file path.
|
||||
# DATABASE_URL=data/db.sqlite3
|
||||
## When using SQLite, this should use the sqlite:// prefix followed by the path
|
||||
## to the DB file. It defaults to sqlite://%DATA_FOLDER%/db.sqlite3.
|
||||
## Bare paths without the sqlite:// prefix are supported for backwards compatibility,
|
||||
## but only if the database file already exists.
|
||||
# DATABASE_URL=sqlite://data/db.sqlite3
|
||||
## When using MySQL, specify an appropriate connection URI.
|
||||
## Details: https://docs.diesel.rs/2.1.x/diesel/mysql/struct.MysqlConnection.html
|
||||
# DATABASE_URL=mysql://user:password@host[:port]/database_name
|
||||
|
||||
@ -507,7 +507,7 @@ make_config! {
|
||||
/// Data folder |> Main data folder
|
||||
data_folder: String, false, def, "data".to_string();
|
||||
/// Database URL
|
||||
database_url: String, false, auto, |c| format!("{}/db.sqlite3", c.data_folder);
|
||||
database_url: String, false, auto, |c| format!("sqlite://{}/db.sqlite3", c.data_folder);
|
||||
/// Icon cache folder
|
||||
icon_cache_folder: String, false, auto, |c| format!("{}/icon_cache", c.data_folder);
|
||||
/// Attachments folder
|
||||
@ -929,14 +929,17 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> {
|
||||
{
|
||||
use crate::db::DbConnType;
|
||||
let url = &cfg.database_url;
|
||||
if DbConnType::from_url(url)? == DbConnType::Sqlite && url.contains('/') {
|
||||
let path = std::path::Path::new(&url);
|
||||
if let Some(parent) = path.parent() {
|
||||
if !parent.is_dir() {
|
||||
err!(format!(
|
||||
"SQLite database directory `{}` does not exist or is not a directory",
|
||||
parent.display()
|
||||
));
|
||||
if DbConnType::from_url(url)? == DbConnType::Sqlite {
|
||||
let file_path = url.strip_prefix("sqlite://").unwrap_or(url);
|
||||
if file_path.contains('/') {
|
||||
let path = std::path::Path::new(file_path);
|
||||
if let Some(parent) = path.parent() {
|
||||
if !parent.is_dir() {
|
||||
err!(format!(
|
||||
"SQLite database directory `{}` does not exist or is not a directory",
|
||||
parent.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,13 +272,32 @@ impl DbConnType {
|
||||
#[cfg(not(postgresql))]
|
||||
err!("`DATABASE_URL` is a PostgreSQL URL, but the 'postgresql' feature is not enabled")
|
||||
|
||||
//Sqlite
|
||||
} else {
|
||||
// Sqlite (explicit)
|
||||
} else if url.len() > 7 && &url[..7] == "sqlite:" {
|
||||
#[cfg(sqlite)]
|
||||
return Ok(DbConnType::Sqlite);
|
||||
|
||||
#[cfg(not(sqlite))]
|
||||
err!("`DATABASE_URL` looks like a SQLite URL, but 'sqlite' feature is not enabled")
|
||||
err!("`DATABASE_URL` is a SQLite URL, but the 'sqlite' feature is not enabled")
|
||||
|
||||
// No recognized scheme — assume legacy bare-path SQLite, but the database file must already exist.
|
||||
// This prevents misconfigured URLs (typos, quoted strings) from silently creating a new empty SQLite database.
|
||||
} else {
|
||||
#[cfg(sqlite)]
|
||||
{
|
||||
if std::path::Path::new(url).exists() {
|
||||
return Ok(DbConnType::Sqlite);
|
||||
}
|
||||
panic!(
|
||||
"`DATABASE_URL` does not match any known database scheme (mysql://, postgresql://, sqlite://) \
|
||||
and no existing SQLite database was found at '{url}'. \
|
||||
If you intend to use SQLite, use an explicit `sqlite://` prefix in your `DATABASE_URL`. \
|
||||
Otherwise, check your DATABASE_URL for typos or quoting issues."
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(sqlite))]
|
||||
err!("`DATABASE_URL` does not match any known database scheme (mysql://, postgresql://, sqlite://)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,11 +409,12 @@ pub fn backup_sqlite() -> Result<String, Error> {
|
||||
|
||||
let db_url = CONFIG.database_url();
|
||||
if DbConnType::from_url(&CONFIG.database_url()).map(|t| t == DbConnType::Sqlite).unwrap_or(false) {
|
||||
// Since we do not allow any schema for sqlite database_url's like `file:` or `sqlite:` to be set, we can assume here it isn't
|
||||
// This way we can set a readonly flag on the opening mode without issues.
|
||||
let mut conn = diesel::sqlite::SqliteConnection::establish(&format!("sqlite://{db_url}?mode=ro"))?;
|
||||
// Strip the sqlite:// prefix if present to get the raw file path
|
||||
let file_path = db_url.strip_prefix("sqlite://").unwrap_or(&db_url);
|
||||
// Open a read-only connection for the backup
|
||||
let mut conn = diesel::sqlite::SqliteConnection::establish(&format!("sqlite://{file_path}?mode=ro"))?;
|
||||
|
||||
let db_path = std::path::Path::new(&db_url).parent().unwrap();
|
||||
let db_path = std::path::Path::new(file_path).parent().unwrap();
|
||||
let backup_file = db_path
|
||||
.join(format!("db_{}.sqlite3", chrono::Utc::now().format("%Y%m%d_%H%M%S")))
|
||||
.to_string_lossy()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user