Skip to content

feat: allow HTTP and HTTPS POST as Backup location#4061

Draft
SNRSE wants to merge 3 commits into
bitfocus:mainfrom
SNRSE:main
Draft

feat: allow HTTP and HTTPS POST as Backup location#4061
SNRSE wants to merge 3 commits into
bitfocus:mainfrom
SNRSE:main

Conversation

@SNRSE

@SNRSE SNRSE commented Apr 1, 2026

Copy link
Copy Markdown

This pull request enhances the backup system by adding support for HTTP/HTTPS push backups, allowing export-type backups to be POSTed directly to a remote server. It also enforces restrictions to prevent raw database backups from being sent to HTTP URLs, and updates the UI to clarify these behaviors.

Backup system improvements:

  • Added support for sending export-type backups (export-json, export-gz, export-yaml) to HTTP/HTTPS endpoints by POSTing the backup data, including a new #pushExportBackup method and a utility to detect HTTP URLs (#isHttpUrl). [1] [2]
  • Enforced that raw database backups (db type) must be saved to a local file path, not an HTTP/HTTPS URL, with error handling for invalid configurations.
  • Modified backup cleanup and deletion logic to avoid attempting to delete remote (HTTP) backups, only removing their references from history. [1] [2]

User interface updates:

  • Updated the backup rule editor UI to clarify that HTTP/HTTPS URLs are not supported for raw database backups, and that export backups can be sent to a remote server via URL. [1] [2]

Codebase cleanup:

  • Removed redundant directory creation logic for HTTP push backups, since no local directory is needed for remote exports.

Summary by CodeRabbit

  • New Features

    • Backup destinations can now be HTTP/HTTPS URLs for export (POST) backups.
  • Changes

    • Raw/database backups remain restricted to local directories; URLs are not supported for db backups.
    • Cleanup and delete operations no longer attempt local file removal for URL-backed backups (history entries are still pruned).
  • Documentation

    • Help text updated to describe URL backup support and db-backup limitation.

SNRSE added 2 commits April 1, 2026 02:56
Add support for pushing export backups to HTTP/HTTPS endpoints and avoid treating remote URLs as local files. Changes include:

- BackupController: detect HTTP/HTTPS backup paths via a new #isHttpUrl helper.
- Create backup: if rule.backupPath is an HTTP(S) URL, reject raw DB backups and call new #pushExportBackup to POST export data to the URL instead of writing a local file.
- #pushExportBackup: generate and stringify export data, determine content type, POST to the target URL, and return a PreviousBackupInfo entry for history trimming.
- Cleanup and deletion logic: when trimming old backups, skip unlinking entries whose filePath is an HTTP(S) URL and only remove the history entry; similarly avoid unlinking remote paths when deleting individual backups.
- UI: update BackupRuleEditor text to clarify that raw DB backups cannot be pushed to HTTP(S) URLs and that export backups may be posted to http:// or https:// endpoints.

These changes enable remote push-style backups for export formats while preserving local file behavior and preventing accidental attempts to write raw DB files to remote URLs.
Copilot AI review requested due to automatic review settings April 1, 2026 11:02
@coderabbitai

coderabbitai Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR extends backup functionality to support HTTP/HTTPS destinations. The backup controller now detects URL-like paths, routes export backups to HTTP endpoints via POST requests, and adjusts cleanup/deletion logic to skip filesystem operations for remote URLs. UI text clarifies the new capability and its limitations.

Changes

Cohort / File(s) Summary
HTTP/HTTPS Backup Support
companion/lib/ImportExport/Backups.ts
Added #isHttpUrl helper and #pushExportBackup method; updated createBackup to route export backups to HTTP/HTTPS URLs, disallow raw DB backups to URL destinations, and changed cleanupOldBackups/deleteBackup to avoid fs.unlink for URL-backed entries while still trimming history.
UI Documentation Updates
webui/src/UserConfig/BackupRuleEditor.tsx
Updated help/warning text to state that http:///https:// URLs may be used for export backups and that raw database backups are not supported to HTTP/HTTPS destinations.

Poem

🌐 Backups whisper to the open net,
Exports packaged, POSTed, and set,
Local files stay where they belong,
URLs hum a distant song,
Cheers to code that moves along ✨

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change—adding HTTP/HTTPS POST support as backup destinations—and is concise and specific.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
companion/lib/ImportExport/Backups.ts (1)

451-456: Optional: Consider authentication header support for future enhancement.

Many backup endpoints require authentication (API keys, bearer tokens, etc.). This might be a nice future enhancement to add a configurable headers field, but it's understandable if that's out of scope for this PR!


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 39ae2dfe-ae8d-4e77-b96b-c4d99e962e62

📥 Commits

Reviewing files that changed from the base of the PR and between 0a7ada7 and f608001.

📒 Files selected for processing (2)
  • companion/lib/ImportExport/Backups.ts
  • webui/src/UserConfig/BackupRuleEditor.tsx

Comment thread companion/lib/ImportExport/Backups.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for “push” backups by allowing export-style backups to be POSTed to HTTP/HTTPS endpoints, while preventing raw database backups from being sent to remote URLs and updating the UI copy to clarify the behavior.

Changes:

  • Add HTTP/HTTPS URL detection and a new push-backup code path that POSTs export backups to a remote server.
  • Prevent db (raw) backups from using HTTP/HTTPS destinations and adjust cleanup/deletion logic to avoid unlinking remote backups.
  • Update the backup rule editor help text/warning to explain URL support and restrictions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
companion/lib/ImportExport/Backups.ts Implements HTTP(S) push backups, URL detection, and adjusts cleanup/delete behavior for remote destinations.
webui/src/UserConfig/BackupRuleEditor.tsx Updates UI messaging to clarify that export backups can be POSTed to URLs, but raw DB backups cannot.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +300 to +305
// Delete old local backup files. For HTTP push backups, only trim history.
const deletionPromises = backupsToDelete.map(async (backup) => {
if (this.#isHttpUrl(backup.filePath)) {
this.#logger.info(`Removed old push backup entry: ${backup.filePath}`)
return backup
}

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

For HTTP push backups this returns the same backup.filePath (the URL) for every backup. Downstream cleanup removes history entries by matching on filePath, so if multiple backups share the same URL it can end up removing all entries (including those that should be kept). Consider storing a unique identifier per push (eg include createdAt and/or backupName in the stored path/id) and/or change cleanup comparison to include createdAt as well as filePath.

Copilot uses AI. Check for mistakes.
// Delete old local backup files. For HTTP push backups, only trim history.
const deletionPromises = backupsToDelete.map(async (backup) => {
if (this.#isHttpUrl(backup.filePath)) {
this.#logger.info(`Removed old push backup entry: ${backup.filePath}`)

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

Logging the full push-backup URL can leak credentials or tokens if the URL contains userinfo/query parameters. Please redact sensitive URL components (eg strip username/password and querystring) before logging.

Suggested change
this.#logger.info(`Removed old push backup entry: ${backup.filePath}`)
let redactedUrl = backup.filePath
try {
const parsedUrl = new URL(backup.filePath)
parsedUrl.username = ''
parsedUrl.password = ''
parsedUrl.search = ''
redactedUrl = parsedUrl.toString()
} catch {
// If parsing fails, fall back to logging the original value
}
this.#logger.info(`Removed old push backup entry: ${redactedUrl}`)

Copilot uses AI. Check for mistakes.
Comment on lines +392 to +396
const backupPath = rule.backupPath ? rule.backupPath : this.#defaultBackupDir
if (this.#isHttpUrl(backupPath)) {
if (rule.backupType === 'db') {
throw new Error('Raw database backups must be saved to a local file path, not HTTP/HTTPS URLs')
}

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

backupPath comes from user input and isn't trimmed before URL detection. A value like ' https://host/path' will fail new URL() parsing and then be treated as a local directory, potentially creating unexpected folders like " https:". Trim whitespace (and ideally validate) before #isHttpUrl and before using it in fs.mkdir/path operations.

Copilot uses AI. Check for mistakes.
Comment on lines +470 to +471
logger.info(`Pushing backup to ${backupUrl}`)

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

This log line includes the full backupUrl, which may contain credentials or tokens. Consider logging a redacted form (eg protocol+host+pathname only) to avoid secrets ending up in logs.

Suggested change
logger.info(`Pushing backup to ${backupUrl}`)
let redactedUrl = backupUrl
try {
const parsed = new URL(backupUrl)
redactedUrl = `${parsed.protocol}//${parsed.host}${parsed.pathname}`
} catch {
// If parsing fails, fall back to the original string
}
logger.info(`Pushing backup to ${redactedUrl}`)

Copilot uses AI. Check for mistakes.
Comment on lines +484 to +487
return {
filePath: backupUrl,
fileSize: typeof exportData.data === 'string' ? Buffer.byteLength(exportData.data) : exportData.data.length,
createdAt: Date.now(),

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

Returning filePath: backupUrl for push backups makes multiple history entries indistinguishable (same filePath each run). That breaks keep-count trimming and makes UI deletion ambiguous because deletion is keyed by filePath. Consider storing a unique per-backup identifier (eg ${backupUrl}#${createdAt} or include the generated filename) and/or extending the delete API to target by both filePath and createdAt.

Suggested change
return {
filePath: backupUrl,
fileSize: typeof exportData.data === 'string' ? Buffer.byteLength(exportData.data) : exportData.data.length,
createdAt: Date.now(),
const createdAt = Date.now()
return {
filePath: `${backupUrl}#${createdAt}`,
fileSize: typeof exportData.data === 'string' ? Buffer.byteLength(exportData.data) : exportData.data.length,
createdAt,

Copilot uses AI. Check for mistakes.
Comment on lines +537 to 545
if (!this.#isHttpUrl(filePath)) {
// Delete the file if it exists
try {
await fs.unlink(filePath)
this.#logger.info(`Deleted backup file: ${filePath}`)
} catch (err) {
this.#logger.warn(`Failed to delete backup file ${filePath}, but will remove from rule: ${err}`)
}
}

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

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

Skipping filesystem deletion for HTTP paths is fine, but the backend removes a history entry by looking up previousBackups via filePath. If multiple push backups share the same URL, the UI can't delete a specific entry (it will remove the first match). Consider updating the delete input to include createdAt (or an explicit id) so push-backup history entries are uniquely addressable.

Copilot uses AI. Check for mistakes.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@CLAassistant

CLAassistant commented Apr 1, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@SNRSE SNRSE changed the title Allow HTTP and HTTPS Backup locations feat: allow HTTP and HTTPS POST as Backup location Apr 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
companion/lib/ImportExport/Backups.ts (1)

538-546: Looks good—just a tiny logging suggestion

The conditional skip for HTTP URLs is exactly right. One small thought: for local files, you log either success or a warning. For HTTP URL entries, there's no log indicating the entry was removed. If you'd like consistency in your logs (especially for debugging/auditing backup history), you could add a brief info log here. Totally optional though!

💡 Optional: Add info log for HTTP entry removal
 			if (!this.#isHttpUrl(filePath)) {
 				// Delete the file if it exists
 				try {
 					await fs.unlink(filePath)
 					this.#logger.info(`Deleted backup file: ${filePath}`)
 				} catch (err) {
 					this.#logger.warn(`Failed to delete backup file ${filePath}, but will remove from rule: ${err}`)
 				}
+			} else {
+				this.#logger.info(`Removing HTTP backup entry from history: ${filePath}`)
 			}

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb670721-81d6-49fa-83a1-5ebeb7519d9b

📥 Commits

Reviewing files that changed from the base of the PR and between f608001 and 3df21c7.

📒 Files selected for processing (1)
  • companion/lib/ImportExport/Backups.ts

@Julusian

Julusian commented Apr 4, 2026

Copy link
Copy Markdown
Member

Thanks for the PR!

I think it would be good to in the ui have a 'storage type' dropdown to pick between 'local' and 'http/https', to make it more explicit.
In the future I expect that more backup targets will be wanted, having this dropdown would prepare for that and perhaps encourage others to consider adding what they need themselves

It would be good to have an alert (like the one that shows when raw database is selected) when set to http mode and raw database is selected, to make it very visual that something is setup wrong

@SNRSE

SNRSE commented Apr 4, 2026

Copy link
Copy Markdown
Author

Thanks for the PR!

I think it would be good to in the ui have a 'storage type' dropdown to pick between 'local' and 'http/https', to make it more explicit. In the future I expect that more backup targets will be wanted, having this dropdown would prepare for that and perhaps encourage others to consider adding what they need themselves

It would be good to have an alert (like the one that shows when raw database is selected) when set to http mode and raw database is selected, to make it very visual that something is setup wrong

Thanks for your Feedback
That was my initial thought as well, but I was hesitant to add another dropdown.
I will work this week to go in that direction, and also incoperate the other feedback

@SNRSE SNRSE marked this pull request as draft April 4, 2026 22:39
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.

4 participants