Skip to content

Add periodic SQLite database backups with compression and encryption#3154

Open
Copilot wants to merge 8 commits intomainfrom
copilot/add-sqlite-dump-feature
Open

Add periodic SQLite database backups with compression and encryption#3154
Copilot wants to merge 8 commits intomainfrom
copilot/add-sqlite-dump-feature

Conversation

Copy link
Contributor

Copilot AI commented Feb 21, 2026

  • Add new parameters to docs/parameters.yaml: Server.DatabaseBackup.Location, Server.DatabaseBackup.Frequency, Server.DatabaseBackup.MaxCount
  • Run go generate to regenerate parameter structs
  • Create database/backup.go implementing backup/restore/periodic goroutine
  • Create database/backup_test.go with comprehensive tests
  • Integrate backup launch into launcher and restore into InitServerDatabase
  • Add CLI commands for backup management (pelican server database backup {create,list,verify,restore,info})
  • PR review feedback:
    • Use errors.Wrap in initServerForBackup to preserve the original error
    • Zero out DEK after use in restoreFromSingleBackup to reduce memory-dump attack window
    • Add SQLite PRAGMA integrity_check validation before restoring a backup
    • Add comment explaining why 0 is passed as server type to InitServerDatabase
    • Capture timestamp once at the start and reuse for both metadata and filename
  • CI fixes:
    • Fix goimports lint: move _ "github.com/glebarez/sqlite" blank import into the third-party group
    • Rebase on main (requires human action — cannot rebase in this environment)
Original prompt

This section details on the original issue you should resolve

<issue_title>Have Pelican make periodic sqlite dumps of it databases</issue_title>
<issue_description>In order to backup the Sqlite databases of Pelican components, we either need to shutdown the service to copy the database file (to ensure to writes are occurring), or have sqlite make an atomic dump of the contents. Other methods of copying a live database and introduce corruption caused by a write operation occurring in the middle of a filesystem (non-sql) read of the file.

Could Pelican create a dumpfile of it's database ~once a day (in a configurable location) to facilitate automatic backups?</issue_description>

<agent_instructions>- Make sure to add tests.

  • Use the Go sqlite library to do the backup.
  • Have a parameter that determines the location of the sqlite backup.
  • Have a parameter for sqlite backup frequency.
  • Have a parameter controlling the count of backups to keep.
  • Compress and encrypt the database backups based on server private key (there are multiple keys; provide a copy of the encryption key encrypted with each).
  • Restore from backup automatically if the primary database is missing but a backup is present.
  • Background goroutines must be managed by an errgroup and cancellable by a context.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits February 21, 2026 02:30
- Add parameters: Server.DatabaseBackup.Location, Server.DatabaseBackup.Frequency, Server.DatabaseBackup.MaxCount
- Create database/backup.go with backup/restore logic using VACUUM INTO
- Compress backups with gzip, encrypt with NaCl box using all issuer keys
- Auto-restore from backup if primary database is missing
- Background backup goroutine managed by errgroup with context cancellation
- Export GetEncryptionKeyPair from config package for reuse
- Add comprehensive tests in database/backup_test.go

Co-authored-by: bbockelm <1093447+bbockelm@users.noreply.github.com>
Co-authored-by: bbockelm <1093447+bbockelm@users.noreply.github.com>
Copilot AI changed the title [WIP] Add periodic sqlite dumps for Pelican databases Add periodic SQLite database backups with compression and encryption Feb 21, 2026
Copilot AI requested a review from bbockelm February 21, 2026 02:35
- Add CLI.  Auto-restore is clever but otherwise denies the ability to do
  things like manually snapshot the files
- Avoid buffering the DB in memory.  Split it into chunks and write them out
- Have a more readable, PEM-encoded format
- Calculate when the first backup should be made from what backups exist, not
  based on server startup.

Plus other small fixes.
@bbockelm bbockelm added enhancement New feature or request client Issue affecting the OSDF client user-request labels Feb 21, 2026
@bbockelm bbockelm requested a review from h2zh February 21, 2026 16:23
@bbockelm bbockelm marked this pull request as ready for review February 21, 2026 16:23
@h2zh h2zh assigned h2zh and unassigned bbockelm, h2zh and Copilot Feb 23, 2026
Copy link
Contributor

@h2zh h2zh left a comment

Choose a reason for hiding this comment

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

@copilot Address these comments

if err := config.InitServer(ctx, server_structs.OriginType); err == nil {
return nil
}
return errors.New("failed to initialize server configuration; ensure a valid pelican.yaml is present")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return errors.New("failed to initialize server configuration; ensure a valid pelican.yaml is present")
return errors.Wrap(err, "failed to initialize server configuration")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8dde278 — now uses errors.Wrap(err, ...) to preserve the underlying error.

decoder := newPEMStreamDecoder(backupFile)

// Phase 1: read ENCRYPTED BACKUP KEY blocks and attempt to decrypt DEK.
var dek [32]byte
Copy link
Contributor

Choose a reason for hiding this comment

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

To reduce the window for memory-dump attacks, this variable should be cleared after use.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8dde278 — added a defer that zeros out all bytes of the dek array after restoreFromSingleBackup returns.

if err := restoredTmp.Close(); err != nil {
return false, errors.Wrap(err, "failed to close restored database file")
}

Copy link
Contributor

Choose a reason for hiding this comment

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

After decrypting and decompressing, it should verify that the result is actually a valid SQLite database. A simple check like trying to open it with database/sql and running PRAGMA integrity_check would prevent corrupted restores.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8dde278 — added verifySQLiteIntegrity() which opens the restored file via database/sql and runs PRAGMA integrity_check before renaming it into place.


// We need the database open for VACUUM INTO.
if database.ServerDatabase == nil {
if err := database.InitServerDatabase(0); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Given the existing struct and function signature:

const (
	CacheType ServerType = 1 << iota
	OriginType
	DirectorType
	RegistryType
	BrokerType
	LocalCacheType
)

func InitServerDatabase(serverType server_structs.ServerType) error {}

For this DB backup CLI command, passing 0 might be an intentional shortcut meaning "just open the database, don't bother with type-specific schema setup." It would be nice to leave a comment to elaborate this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8dde278 — added a comment explaining that passing 0 opens the database without running type-specific migrations, which is appropriate for backup commands that only need read access.

}

// Atomically move the temporary file to the final backup path.
timestamp := time.Now().UTC().Format(backupTimestampFormat)
Copy link
Contributor

Choose a reason for hiding this comment

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

The metadata timestamp and filename timestamp are generated at different times. If the backup takes a while (large DB), they could diverge by seconds or more. The timestamp should be captured once and reused for both.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8dde278backupTime is now captured once at the start of createBackup and passed to both collectBackupMetadata and the filename formatter.

Copy link
Contributor

@h2zh h2zh left a comment

Choose a reason for hiding this comment

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

@copilot Address these comments

…integrity check, timestamp consistency, and server type comment

Co-authored-by: h2zh <61568048+h2zh@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 25, 2026

@copilot Address these comments

All five review comments addressed in 8dde278.

@h2zh
Copy link
Contributor

h2zh commented Feb 27, 2026

@copilot fix the failed CIs: rebase on main and fix the linter issues (suggestion: run pre-commit run --all-files)

Co-authored-by: h2zh <61568048+h2zh@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 27, 2026

@copilot fix the failed CIs: rebase on main and fix the linter issues (suggestion: run pre-commit run --all-files)

Fixed the goimports lint issue in a23997d — moved _ "github.com/glebarez/sqlite" blank import into the third-party imports group where it belongs.

For the rebase on main: I'm unable to perform git rebase operations in my environment. Could you rebase the branch on main?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client Issue affecting the OSDF client enhancement New feature or request user-request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Have Pelican make periodic sqlite dumps of it databases

3 participants