Skip to content

feat: add database backup settings#158

Merged
lcomplete merged 1 commit into
mainfrom
codex/add-database-backup
May 24, 2026
Merged

feat: add database backup settings#158
lcomplete merged 1 commit into
mainfrom
codex/add-database-backup

Conversation

@lcomplete
Copy link
Copy Markdown
Owner

Summary

  • add configurable SQLite backup path and retention settings
  • add scheduled database backup task using SQLite VACUUM INTO
  • expose backup settings in the general settings UI with English and Chinese labels

Tests

  • ./mvnw -pl huntly-server -Dtest=DatabaseBackupTaskTest test

@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented May 21, 2026

🤖 Augment PR Summary

Summary: This PR introduces configurable SQLite database backups (path + retention) and wires the settings through the backend and UI.

Changes:

  • Adds backupPath and backupKeepDays to GlobalSetting (server entity + client API typing).
  • Updates the General Settings UI to let users configure backup directory and retention days (with EN/zh-CN labels and help text).
  • Implements a scheduled task (DatabaseBackupTask) that runs daily at 2:00 AM and performs backups via SQLite VACUUM INTO.
  • Adds retention cleanup that deletes backups older than the configured number of days (defaulting to 30 when unset/invalid).
  • Adds a focused unit test covering the scheduled task flow and cleanup behavior.

Technical Notes: Backup is disabled when the backup path is empty; server-side path normalization/validation is applied before persisting settings.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 5 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

if (StringUtils.isNotBlank(globalSetting.getBackupPath())) {
java.nio.file.Path normalized = java.nio.file.Paths.get(globalSetting.getBackupPath()).normalize();
if (!normalized.isAbsolute()) {
throw new IllegalArgumentException("backup path must be an absolute path");
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GlobalSettingService.saveGlobalSetting: throwing IllegalArgumentException here will be handled by CustomExceptionHandler's generic Exception handler and returned as HTTP 500, so invalid user input becomes a server error. Consider mapping this validation failure (and potential InvalidPathException from Paths.get(...)) to a request/parameter exception so the client gets a 4xx and an actionable message.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

if (!normalized.isAbsolute()) {
throw new IllegalArgumentException("backup path must be an absolute path");
}
if (normalized.toString().contains("..")) {
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalized.toString().contains("..") is a brittle traversal check (especially after normalize()) and can also reject legitimate absolute paths that contain .. in a directory name (e.g., foo..bar). This can block valid backup directories while not reliably enforcing the intended constraint.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

String backupPath = setting.getBackupPath();
File backupDir = new File(backupPath);
if (!backupDir.exists()) {
if (!backupDir.mkdirs()) {
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DatabaseBackupTask.autoBackupDatabase: if backupPath exists but is a regular file (not a directory), this code will proceed and the backup will likely fail later with a confusing error. Consider explicitly checking backupDir.isDirectory() and handling that case.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

File backupFile = new File(backupDir, "db_backup_" + timestamp + ".sqlite");
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("VACUUM INTO ?")) {
stmt.setString(1, backupFile.getAbsolutePath());
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please double-check that the SQLite JDBC driver supports binding a placeholder in VACUUM INTO ?; some SQLite statements require a string literal and will fail at runtime with parameters. If placeholders aren't supported, backups would fail in production even though the unit test passes (it mocks the statement).

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

autoSaveSiteBlacklists: yup.string().nullable()
autoSaveSiteBlacklists: yup.string().nullable(),
backupPath: yup.string().nullable(),
backupKeepDays: yup.number().nullable().min(1, t('backupKeepDaysMin'))
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Yup schema, yup.number().nullable() doesn't treat an empty string (common from <input type="number">) as null, so clearing backupKeepDays can become NaN and cause unexpected validation/submission behavior. This may conflict with the UI hint that leaving retention empty defaults to 30 days.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@lcomplete lcomplete merged commit 75922b5 into main May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant