(bugfix) preserve saved global backup root in Django setup form
Fix the global config edit view so default initial values are only used when creating a new config, preventing saved backup_root values from being hidden by form defaults. Keep pobsync_home as an internal runtime setting instead of exposing it in the normal Django setup form. Mount a host backup directory into Docker at /backups and document POBSYNC_BACKUP_ROOT so backup_root behaves predictably in containers.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@ __pycache__/
|
|||||||
*.py[cod]
|
*.py[cod]
|
||||||
.venv/
|
.venv/
|
||||||
var/
|
var/
|
||||||
|
backups/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
|||||||
@@ -129,6 +129,15 @@ docker compose up --build web scheduler
|
|||||||
```
|
```
|
||||||
|
|
||||||
The container persists `/opt/pobsync` and the SQLite database in Docker volumes.
|
The container persists `/opt/pobsync` and the SQLite database in Docker volumes.
|
||||||
|
Backup data is mounted at `/backups` inside the containers. By default this uses `./backups` on the host.
|
||||||
|
Override it with `POBSYNC_BACKUP_ROOT`:
|
||||||
|
|
||||||
|
```
|
||||||
|
POBSYNC_BACKUP_ROOT=/mnt/backups/pobsync docker compose up --build web scheduler
|
||||||
|
```
|
||||||
|
|
||||||
|
In the Django global config, set the backup root to `/backups` when running in Docker. For local, non-Docker use,
|
||||||
|
set it directly to the host path, for example `/mnt/backups/pobsync`.
|
||||||
|
|
||||||
## Docker With MariaDB
|
## Docker With MariaDB
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- pobsync_state:/opt/pobsync
|
- pobsync_state:/opt/pobsync
|
||||||
- pobsync_db:/var/lib/pobsync
|
- pobsync_db:/var/lib/pobsync
|
||||||
|
- ${POBSYNC_BACKUP_ROOT:-./backups}:/backups
|
||||||
|
|
||||||
scheduler:
|
scheduler:
|
||||||
build: .
|
build: .
|
||||||
@@ -26,6 +27,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- pobsync_state:/opt/pobsync
|
- pobsync_state:/opt/pobsync
|
||||||
- pobsync_db:/var/lib/pobsync
|
- pobsync_db:/var/lib/pobsync
|
||||||
|
- ${POBSYNC_BACKUP_ROOT:-./backups}:/backups
|
||||||
|
|
||||||
web-mariadb:
|
web-mariadb:
|
||||||
profiles: ["mariadb"]
|
profiles: ["mariadb"]
|
||||||
@@ -48,6 +50,7 @@ services:
|
|||||||
- "8010:8000"
|
- "8010:8000"
|
||||||
volumes:
|
volumes:
|
||||||
- pobsync_state:/opt/pobsync
|
- pobsync_state:/opt/pobsync
|
||||||
|
- ${POBSYNC_BACKUP_ROOT:-./backups}:/backups
|
||||||
|
|
||||||
scheduler-mariadb:
|
scheduler-mariadb:
|
||||||
profiles: ["mariadb"]
|
profiles: ["mariadb"]
|
||||||
@@ -68,6 +71,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
volumes:
|
volumes:
|
||||||
- pobsync_state:/opt/pobsync
|
- pobsync_state:/opt/pobsync
|
||||||
|
- ${POBSYNC_BACKUP_ROOT:-./backups}:/backups
|
||||||
|
|
||||||
db:
|
db:
|
||||||
profiles: ["mariadb"]
|
profiles: ["mariadb"]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from .models import GlobalConfig, HostConfig, ScheduleConfig
|
from .models import GlobalConfig, HostConfig, ScheduleConfig
|
||||||
from .scheduler import parse_cron_expr
|
from .scheduler import parse_cron_expr
|
||||||
@@ -84,7 +85,6 @@ class GlobalConfigForm(forms.ModelForm):
|
|||||||
fields = (
|
fields = (
|
||||||
"name",
|
"name",
|
||||||
"backup_root",
|
"backup_root",
|
||||||
"pobsync_home",
|
|
||||||
"ssh_user",
|
"ssh_user",
|
||||||
"ssh_port",
|
"ssh_port",
|
||||||
"ssh_options",
|
"ssh_options",
|
||||||
@@ -104,11 +104,18 @@ class GlobalConfigForm(forms.ModelForm):
|
|||||||
help_texts = {
|
help_texts = {
|
||||||
"name": "Usually 'default'. The backup engine currently reads the default config.",
|
"name": "Usually 'default'. The backup engine currently reads the default config.",
|
||||||
"backup_root": "Directory that contains host backup folders.",
|
"backup_root": "Directory that contains host backup folders.",
|
||||||
"pobsync_home": "Base directory for runtime state inside the container or host.",
|
|
||||||
"default_source_root": "Used by hosts without a custom source root.",
|
"default_source_root": "Used by hosts without a custom source root.",
|
||||||
"default_destination_subdir": "Optional subdirectory below each snapshot.",
|
"default_destination_subdir": "Optional subdirectory below each snapshot.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def save(self, commit: bool = True):
|
||||||
|
instance = super().save(commit=False)
|
||||||
|
instance.pobsync_home = settings.POBSYNC_HOME
|
||||||
|
if commit:
|
||||||
|
instance.save()
|
||||||
|
self.save_m2m()
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class ScheduleConfigForm(forms.ModelForm):
|
class ScheduleConfigForm(forms.ModelForm):
|
||||||
cron_expr = forms.CharField(
|
cron_expr = forms.CharField(
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ class ViewTests(TestCase):
|
|||||||
{
|
{
|
||||||
"name": "default",
|
"name": "default",
|
||||||
"backup_root": "/backups",
|
"backup_root": "/backups",
|
||||||
"pobsync_home": "/opt/pobsync",
|
|
||||||
"ssh_user": "backup",
|
"ssh_user": "backup",
|
||||||
"ssh_port": "2222",
|
"ssh_port": "2222",
|
||||||
"ssh_options": "StrictHostKeyChecking=no\nBatchMode=yes",
|
"ssh_options": "StrictHostKeyChecking=no\nBatchMode=yes",
|
||||||
@@ -100,6 +99,7 @@ class ViewTests(TestCase):
|
|||||||
self.assertContains(response, "Global config saved for default.")
|
self.assertContains(response, "Global config saved for default.")
|
||||||
config = GlobalConfig.objects.get(name="default")
|
config = GlobalConfig.objects.get(name="default")
|
||||||
self.assertEqual(config.backup_root, "/backups")
|
self.assertEqual(config.backup_root, "/backups")
|
||||||
|
self.assertEqual(config.pobsync_home, "/opt/pobsync")
|
||||||
self.assertEqual(config.ssh_user, "backup")
|
self.assertEqual(config.ssh_user, "backup")
|
||||||
self.assertEqual(config.ssh_port, 2222)
|
self.assertEqual(config.ssh_port, 2222)
|
||||||
self.assertEqual(config.ssh_options, ["StrictHostKeyChecking=no", "BatchMode=yes"])
|
self.assertEqual(config.ssh_options, ["StrictHostKeyChecking=no", "BatchMode=yes"])
|
||||||
@@ -109,6 +109,21 @@ class ViewTests(TestCase):
|
|||||||
self.assertEqual(config.retention_daily, 7)
|
self.assertEqual(config.retention_daily, 7)
|
||||||
self.assertEqual(config.retention_yearly, 1)
|
self.assertEqual(config.retention_yearly, 1)
|
||||||
|
|
||||||
|
def test_global_config_form_renders_saved_backup_root_on_edit(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.get(reverse("edit_global_config"))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, "/mnt/pobsync/backups")
|
||||||
|
self.assertNotContains(response, "/opt/pobsync/backups")
|
||||||
|
self.assertNotContains(response, "Pobsync home")
|
||||||
|
|
||||||
def test_create_host_config_form_creates_host(self) -> None:
|
def test_create_host_config_form_creates_host(self) -> None:
|
||||||
self.client.force_login(self.staff_user)
|
self.client.force_login(self.staff_user)
|
||||||
|
|
||||||
@@ -196,7 +211,7 @@ class ViewTests(TestCase):
|
|||||||
self.assertContains(response, "web-01")
|
self.assertContains(response, "web-01")
|
||||||
self.assertContains(response, "success")
|
self.assertContains(response, "success")
|
||||||
self.assertContains(response, "ABCDEFGH")
|
self.assertContains(response, "ABCDEFGH")
|
||||||
self.assertContains(response, '"ok": true')
|
self.assertContains(response, ""ok": true")
|
||||||
self.assertContains(response, reverse("snapshot_detail", args=[snapshot.id]))
|
self.assertContains(response, reverse("snapshot_detail", args=[snapshot.id]))
|
||||||
|
|
||||||
def test_snapshot_detail_renders_metadata_runs_and_children(self) -> None:
|
def test_snapshot_detail_renders_metadata_runs_and_children(self) -> None:
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ def edit_global_config(request):
|
|||||||
messages.success(request, f"Global config saved for {saved_config.name}.")
|
messages.success(request, f"Global config saved for {saved_config.name}.")
|
||||||
return redirect("dashboard")
|
return redirect("dashboard")
|
||||||
else:
|
else:
|
||||||
form = GlobalConfigForm(instance=global_config, initial=_default_global_initial())
|
form = GlobalConfigForm(instance=global_config) if global_config else GlobalConfigForm(initial=_default_global_initial())
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
@@ -235,8 +235,7 @@ def _default_schedule_initial() -> dict[str, object]:
|
|||||||
def _default_global_initial() -> dict[str, object]:
|
def _default_global_initial() -> dict[str, object]:
|
||||||
return {
|
return {
|
||||||
"name": "default",
|
"name": "default",
|
||||||
"backup_root": "/opt/pobsync/backups",
|
"backup_root": "/backups",
|
||||||
"pobsync_home": "/opt/pobsync",
|
|
||||||
"ssh_user": "root",
|
"ssh_user": "root",
|
||||||
"ssh_port": 22,
|
"ssh_port": 22,
|
||||||
"rsync_binary": "rsync",
|
"rsync_binary": "rsync",
|
||||||
|
|||||||
Reference in New Issue
Block a user