summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSadie Powell <sadie@witchery.services>2025-04-15 13:00:34 +0100
committerSadie Powell <sadie@witchery.services>2025-04-15 13:00:34 +0100
commitbd9d3b0f7d83256f97940043693fd52a62c6198a (patch)
tree5cd1ebdad7c0e4e3e655e2c39e0a033623d5c999
parentcbb41241d4ecfe74d701edf100df6260dfbce1ce (diff)
Add support for monthly backups to db_json.
-rw-r--r--data/anope.example.conf7
-rw-r--r--modules/database/db_json.cpp102
2 files changed, 61 insertions, 48 deletions
diff --git a/data/anope.example.conf b/data/anope.example.conf
index c21aa343f..4c09f3a5e 100644
--- a/data/anope.example.conf
+++ b/data/anope.example.conf
@@ -1165,14 +1165,15 @@ module
module_database = "{name}.module.json"
/*
- * Sets how many days worth of backups should be kept.
+ * Sets how many days and months worth of backups should be kept.
*
* It is recommended that at the very least you keep one backup. Failure to
* do so may result in total data loss if you ever run out of disk space or
* have a power failure during a database write. However, if you're *REALLY*
- * sure this won't happen you can disable backups by setting this to 0.
+ * sure this won't happen you can disable backups by setting these to 0.
*/
- backups = 14
+ daily_backups = 7
+ monthly_backups = 3
/*
* The directory in which backups are kept.
diff --git a/modules/database/db_json.cpp b/modules/database/db_json.cpp
index a3587a2dc..e1523c31d 100644
--- a/modules/database/db_json.cpp
+++ b/modules/database/db_json.cpp
@@ -10,6 +10,7 @@
*/
#include <filesystem>
+namespace fs = std::filesystem;
#include "yyjson/yyjson.c"
@@ -99,6 +100,55 @@ private:
// Whether OnLoadDatabase has been called yet.
bool loaded = false;
+ void CreateBackup(const Anope::string &backupdir, const fs::path &dbpath, size_t backups, const char *fmt, const char *glob)
+ {
+ if (!backups)
+ return;
+
+ char timebuf[16];
+ strftime(timebuf, sizeof(timebuf), fmt, localtime(&Anope::CurTime));
+
+ auto backupbase = Anope::Expand(backupdir, dbpath.filename().string()) + ".";
+ auto backupname = backupbase + timebuf;
+
+ std::error_code ec;
+ Anope::string dbname = dbpath.string();
+ Log(LOG_DEBUG) << "Copying " << dbname << " to " << backupname;
+ if (!fs::copy_file(dbname.str(), backupname.str(), fs::copy_options::overwrite_existing, ec))
+ {
+ Log(this) << "Failed to copy " << dbname << " to " << backupname << ": " << ec.message();
+ if (!Config->GetModule(this).Get<bool>("ignore_backup_failure"))
+ {
+ Anope::Quitting = true;
+ Anope::QuitReason = "Failed to copy " + dbname + " to " + backupname + ": " + ec.message();
+ }
+ }
+
+ // Delete older backups.
+ std::set<Anope::string> old_backups;
+ for (const auto &entry : fs::directory_iterator(backupdir.str(), ec))
+ {
+ Anope::string entryname = entry.path().string();
+ if (entryname.compare(0, backupbase.length(), backupbase) != 0)
+ continue; // Not one of our backups.
+
+ if (!Anope::Match(entryname.substr(backupbase.length()), glob))
+ continue; // Not this type of backup.
+
+ old_backups.insert(entryname);
+ if (old_backups.size() <= backups)
+ continue;
+
+ Log(LOG_DEBUG) << "Deleting expired backup " << *old_backups.begin();
+ if (!fs::remove(old_backups.begin()->str(), ec))
+ {
+ Log(this) << "Failed to delete expired backup " << *old_backups.begin() << ": " << ec.message();
+ break;
+ }
+ old_backups.erase(old_backups.begin());
+ }
+ }
+
Anope::string GetDatabaseFile(Module *mod)
{
Anope::string filename;
@@ -121,19 +171,16 @@ private:
void BackupDatabase(const Anope::string &dbname)
{
- namespace fs = std::filesystem;
-
std::error_code ec;
fs::path dbpath(dbname.str());
if (!fs::exists(dbpath, ec) || ec)
return; // Nothing to backup.
auto &modconf = Config->GetModule(this);
- auto backups = modconf.Get<unsigned>("backups", "14");
- if (!backups)
- return; // No backups
-
- auto ignore_backup_failure = modconf.Get<bool>("ignore_backup_failure");
+ auto daily_backups = modconf.Get<size_t>("daily_backups", "7");
+ auto monthly_backups = modconf.Get<size_t>("monthly_backups", "3");
+ if (!daily_backups && !monthly_backups)
+ return; // No backups.
auto backupdir = Anope::ExpandData(modconf.Get<Anope::string>("backup_directory", "backups"));
if (!fs::is_directory(backupdir.str(), ec) && !ec)
@@ -142,7 +189,7 @@ private:
if (ec)
{
Log(this) << "Failed to create backup directory: " << ec.message();
- if (!ignore_backup_failure)
+ if (!modconf.Get<bool>("ignore_backup_failure"))
{
Anope::Quitting = true;
Anope::QuitReason = "Failed to create backup directory: " + ec.message();
@@ -151,43 +198,8 @@ private:
}
}
- char timebuf[16];
- strftime(timebuf, sizeof(timebuf), "%Y-%m-%d", localtime(&Anope::CurTime));
-
- auto backupbase = Anope::Expand(backupdir, dbpath.filename().string()) + ".";
- auto backupname = backupbase + timebuf;
-
- Log(LOG_DEBUG) << "Copying " << dbname << " to " << backupname;
- if (!fs::copy_file(dbname.str(), backupname.str(), fs::copy_options::overwrite_existing, ec))
- {
- Log(this) << "Failed to copy " << dbname << " to " << backupname << ": " << ec.message();
- if (!ignore_backup_failure)
- {
- Anope::Quitting = true;
- Anope::QuitReason = "Failed to copy " + dbname + " to " + backupname + ": " + ec.message();
- }
- }
-
- // Delete older backups.
- std::set<Anope::string> old_backups;
- for (const auto &entry : fs::directory_iterator(backupdir.str(), ec))
- {
- Anope::string entryname = entry.path().string();
- if (entryname.compare(0, backupbase.length(), backupbase) != 0)
- continue; // Not one of our backups.
-
- old_backups.insert(entryname);
- if (old_backups.size() <= backups)
- continue;
-
- Log(LOG_DEBUG) << "Deleting expired backup " << *old_backups.begin();
- if (!fs::remove(old_backups.begin()->str(), ec))
- {
- Log(this) << "Failed to delete expired backup " << *old_backups.begin() << ": " << ec.message();
- break;
- }
- old_backups.erase(old_backups.begin());
- }
+ CreateBackup(backupdir, dbpath, daily_backups, "%Y-%m-%d", "\?\?\?\?-\?\?-\?\?");
+ CreateBackup(backupdir, dbpath, monthly_backups, "%Y-%m", "\?\?\?\?-\?\?");
}
DBPair ReadDatabase(const Anope::string &dbname)