(refactor) make Docker backup root static in Django setup

Remove backup_root from the normal Django global config form and display
the fixed container path /backups instead.

Always persist /backups from the setup form so Docker deployments do not
mix host paths with container paths.

Update tests and docs to clarify that the host backup directory is chosen
through the Docker mount, while Django always uses /backups internally.
This commit is contained in:
2026-05-19 13:14:22 +02:00
parent 3da877eb8a
commit 573177e118
7 changed files with 53 additions and 10 deletions

View File

@@ -84,7 +84,6 @@ class GlobalConfigForm(forms.ModelForm):
model = GlobalConfig
fields = (
"name",
"backup_root",
"ssh_user",
"ssh_port",
"ssh_options",
@@ -103,13 +102,13 @@ class GlobalConfigForm(forms.ModelForm):
)
help_texts = {
"name": "Usually 'default'. The backup engine currently reads the default config.",
"backup_root": "Directory that contains host backup folders.",
"default_source_root": "Used by hosts without a custom source root.",
"default_destination_subdir": "Optional subdirectory below each snapshot.",
}
def save(self, commit: bool = True):
instance = super().save(commit=False)
instance.backup_root = settings.POBSYNC_BACKUP_ROOT
instance.pobsync_home = settings.POBSYNC_HOME
if commit:
instance.save()

View File

@@ -78,6 +78,7 @@
.status.failed { color: var(--failed); border-color: #e8b4b4; background: #fff0f0; }
.status.running { color: var(--running); border-color: #e7cf8a; background: #fff8df; }
.stack { display: grid; gap: 4px; }
.stack.spaced { margin-bottom: 14px; }
.two-col { display: grid; gap: 18px; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.actions { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 18px; }
.actions.inline { margin: 12px 0 0; }

View File

@@ -11,6 +11,10 @@
<section class="panel">
<h2>{% if global_config %}Edit Global Config{% else %}Create Global Config{% endif %}</h2>
<div class="stack spaced">
<div><strong>Backup root:</strong> {{ backup_root }}</div>
<div class="muted">This is the fixed path inside the Docker containers. Change the host directory by changing the Docker mount.</div>
</div>
<form method="post" class="form-grid">
{% csrf_token %}
{{ form.non_field_errors }}

View File

@@ -75,7 +75,6 @@ class ViewTests(TestCase):
reverse("edit_global_config"),
{
"name": "default",
"backup_root": "/backups",
"ssh_user": "backup",
"ssh_port": "2222",
"ssh_options": "StrictHostKeyChecking=no\nBatchMode=yes",
@@ -109,7 +108,7 @@ class ViewTests(TestCase):
self.assertEqual(config.retention_daily, 7)
self.assertEqual(config.retention_yearly, 1)
def test_global_config_form_renders_saved_backup_root_on_edit(self) -> None:
def test_global_config_form_renders_static_container_backup_root_on_edit(self) -> None:
self.client.force_login(self.staff_user)
GlobalConfig.objects.create(
name="default",
@@ -120,10 +119,48 @@ class ViewTests(TestCase):
response = self.client.get(reverse("edit_global_config"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "/mnt/pobsync/backups")
self.assertContains(response, "Backup root:")
self.assertContains(response, "/backups")
self.assertNotContains(response, "/mnt/pobsync/backups")
self.assertNotContains(response, "/opt/pobsync/backups")
self.assertNotContains(response, "Pobsync home")
def test_global_config_form_resets_backup_root_to_static_container_path(self) -> None:
self.client.force_login(self.staff_user)
GlobalConfig.objects.create(
name="default",
backup_root="/mnt/pobsync/backups",
pobsync_home="/custom/legacy/home",
)
response = self.client.post(
reverse("edit_global_config"),
{
"name": "default",
"ssh_user": "root",
"ssh_port": "22",
"ssh_options": "",
"rsync_binary": "rsync",
"rsync_args": "",
"rsync_extra_args": "",
"rsync_timeout_seconds": "0",
"rsync_bwlimit_kbps": "0",
"default_source_root": "/",
"default_destination_subdir": "",
"excludes_default": "",
"retention_daily": "14",
"retention_weekly": "8",
"retention_monthly": "12",
"retention_yearly": "0",
},
follow=True,
)
self.assertRedirects(response, reverse("dashboard"))
config = GlobalConfig.objects.get(name="default")
self.assertEqual(config.backup_root, "/backups")
self.assertEqual(config.pobsync_home, "/opt/pobsync")
def test_create_host_config_form_creates_host(self) -> None:
self.client.force_login(self.staff_user)

View File

@@ -4,6 +4,7 @@ import json
from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required
from django.conf import settings
from django.db.models import Count
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_POST
@@ -60,6 +61,7 @@ def edit_global_config(request):
{
"global_config": global_config,
"form": form,
"backup_root": settings.POBSYNC_BACKUP_ROOT,
},
)
@@ -264,7 +266,6 @@ def _default_schedule_initial() -> dict[str, object]:
def _default_global_initial() -> dict[str, object]:
return {
"name": "default",
"backup_root": "/backups",
"ssh_user": "root",
"ssh_port": 22,
"rsync_binary": "rsync",

View File

@@ -89,3 +89,4 @@ STATIC_ROOT = os.getenv("POBSYNC_STATIC_ROOT", str(BASE_DIR / "var" / "static"))
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
POBSYNC_HOME = os.getenv("POBSYNC_HOME", "/opt/pobsync")
POBSYNC_BACKUP_ROOT = "/backups"