summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2024-10-10 01:01:05 -0400
committerOwen Jacobson <owen@grimoire.ca>2024-10-10 01:06:32 -0400
commitd173bc08f2b699f58c8cca752ff688ad46f33ced (patch)
treedd57554f3bf0ba0e8b57a65c31eac55c59feb9f9
parent319721717331bb414f86543a4ea1a115c25329a0 (diff)
Automatically delete database backups if automatic restore is successful.
Operational experience with the server has shown that leaving the backup in place is not helpful. The near-automatic choice is to immediately delete it, and the server won't start until it has been deleted. If the backup restore succeeded, then we know the user has a copy of their database, since the sqlite3 online backups API promises to make the target database bitwise-identical to the source database, so there's little chance the user will need a duplicate.
-rw-r--r--docs/ops.md8
-rw-r--r--src/db/mod.rs8
2 files changed, 13 insertions, 3 deletions
diff --git a/docs/ops.md b/docs/ops.md
index 8f21c79..02644c2 100644
--- a/docs/ops.md
+++ b/docs/ops.md
@@ -2,6 +2,10 @@
## Upgrades
-`hi` will automatically upgrade its database on startup. Before doing so, it will create a backup of your database (at `.hi.backup`, or controlled by `--backup-database-url`). If the migration process succeeds, this backup will be deleted automatically. If the migration process _fails_, however, the backup will be left in place. In addition, `hi` will attempt to restore your existing database from the backup before exiting.
+`hi` will automatically upgrade its database on startup. Before doing so, it will create a backup of your database (at `.hi.backup`, or controlled by `--backup-database-url`). If the migration process succeeds, this backup will be deleted automatically. If the migration process _fails_, however, `hi` will attempt to restore your existing database from the backup before exiting. If the restore process also fails, then both the backup database and the suspected-broken database will be left in place.
-`hi` will not start if the backup database already exists. To restart `hi` after a failure, move the backup database aside. Once you are satisfied that `hi` has recovered successfully, you can delete it. If you need to restore the database manually, you can also copy it overtop of your database using normal filesystem tools (`cp -a .hi.backup. hi`, for example).
+To avoid destroying backups that may still be needed, `hi` will not start if the backup database already exists. **There is no catch-all advice on how to proceed**, but you can try the following:
+
+* Start the server with **a copy** of the backup database, and determine if any data has been lost. If not, shut it down, replace your main database by copying the backup, and carry on.
+
+The `hi` database is an ordinary file. While the server is not running, it can be freely copied or renamed without invalidating the data in it.
diff --git a/src/db/mod.rs b/src/db/mod.rs
index bbaec7d..b9c59ef 100644
--- a/src/db/mod.rs
+++ b/src/db/mod.rs
@@ -28,6 +28,8 @@ pub async fn prepare(url: &str, backup_url: &str) -> Result<SqlitePool, Error> {
if let Err(migrate_error) = sqlx::migrate!().run(&pool).await {
if let Err(restore_error) = backup::Backup::from(&backup_pool).to(&pool).backup().await {
Err(Error::Restore(restore_error, migrate_error))?;
+ } else if let Err(drop_error) = Sqlite::drop_database(backup_url).await {
+ Err(Error::Drop(drop_error, migrate_error))?;
} else {
Err(migrate_error)?;
};
@@ -77,8 +79,12 @@ pub enum Error {
/// Failure due to a database backup error. See [`backup::Error`].
#[error(transparent)]
Backup(#[from] backup::Error),
- #[error("backing out failed migration also failed: {0} ({1})")]
+ #[error("migration failed: {1}\nrestoring backup failed: {0}")]
Restore(backup::Error, sqlx::migrate::MigrateError),
+ #[error(
+ "migration failed: {1}\nrestoring from backup succeeded, but deleting backup failed: {0}"
+ )]
+ Drop(sqlx::Error, sqlx::migrate::MigrateError),
/// Failure due to a database migration error. See
/// [`sqlx::migrate::MigrateError`].
#[error(transparent)]