Files
pobsync/src/pobsync_backend/models.py

159 lines
6.3 KiB
Python
Raw Normal View History

from __future__ import annotations
from django.db import models
class TimestampedModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class GlobalConfig(TimestampedModel):
name = models.CharField(max_length=64, default="default", unique=True)
backup_root = models.CharField(max_length=512)
pobsync_home = models.CharField(max_length=512, default="/opt/pobsync")
ssh_user = models.CharField(max_length=64, default="root")
ssh_port = models.PositiveIntegerField(default=22)
ssh_options = models.JSONField(default=list, blank=True)
rsync_binary = models.CharField(max_length=128, default="rsync")
rsync_args = models.JSONField(default=list, blank=True)
rsync_extra_args = models.JSONField(default=list, blank=True)
rsync_timeout_seconds = models.PositiveIntegerField(default=0)
rsync_bwlimit_kbps = models.PositiveIntegerField(default=0)
default_source_root = models.CharField(max_length=512, default="/")
default_destination_subdir = models.CharField(max_length=512, default="", blank=True)
excludes_default = models.JSONField(default=list, blank=True)
retention_daily = models.PositiveIntegerField(default=14)
retention_weekly = models.PositiveIntegerField(default=8)
retention_monthly = models.PositiveIntegerField(default=12)
retention_yearly = models.PositiveIntegerField(default=0)
data = models.JSONField(default=dict, blank=True)
class Meta:
verbose_name = "global config"
verbose_name_plural = "global configs"
def __str__(self) -> str:
return self.name
class HostConfig(TimestampedModel):
host = models.CharField(max_length=255, unique=True)
address = models.CharField(max_length=255)
enabled = models.BooleanField(default=True)
ssh_user = models.CharField(max_length=64, blank=True)
ssh_port = models.PositiveIntegerField(null=True, blank=True)
source_root = models.CharField(max_length=512, blank=True)
includes = models.JSONField(default=list, blank=True)
excludes_add = models.JSONField(default=list, blank=True)
excludes_replace = models.JSONField(null=True, blank=True)
rsync_extra_args = models.JSONField(default=list, blank=True)
retention_daily = models.PositiveIntegerField(default=14)
retention_weekly = models.PositiveIntegerField(default=8)
retention_monthly = models.PositiveIntegerField(default=12)
retention_yearly = models.PositiveIntegerField(default=0)
config = models.JSONField(default=dict, blank=True)
class Meta:
ordering = ["host"]
def __str__(self) -> str:
return self.host
class BackupRun(models.Model):
class RunType(models.TextChoices):
SCHEDULED = "scheduled", "Scheduled"
MANUAL = "manual", "Manual"
class Status(models.TextChoices):
QUEUED = "queued", "Queued"
RUNNING = "running", "Running"
SUCCESS = "success", "Success"
FAILED = "failed", "Failed"
CANCELLED = "cancelled", "Cancelled"
host = models.ForeignKey(HostConfig, on_delete=models.PROTECT, related_name="runs")
run_type = models.CharField(max_length=16, choices=RunType.choices, default=RunType.SCHEDULED)
status = models.CharField(max_length=16, choices=Status.choices, default=Status.QUEUED)
started_at = models.DateTimeField(null=True, blank=True)
ended_at = models.DateTimeField(null=True, blank=True)
snapshot_path = models.CharField(max_length=1024, blank=True)
snapshot = models.ForeignKey(
"SnapshotRecord",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="backup_runs",
)
base_path = models.CharField(max_length=1024, blank=True)
rsync_exit_code = models.IntegerField(null=True, blank=True)
result = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
def __str__(self) -> str:
return f"{self.host} {self.run_type} {self.status}"
class SnapshotRecord(models.Model):
class Kind(models.TextChoices):
SCHEDULED = "scheduled", "Scheduled"
MANUAL = "manual", "Manual"
INCOMPLETE = "incomplete", "Incomplete"
host = models.ForeignKey(HostConfig, on_delete=models.CASCADE, related_name="snapshots")
kind = models.CharField(max_length=16, choices=Kind.choices)
dirname = models.CharField(max_length=255)
path = models.CharField(max_length=1024)
2026-05-19 11:19:22 +02:00
base = models.ForeignKey(
"self",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="derived_snapshots",
)
base_kind = models.CharField(max_length=16, blank=True)
base_dirname = models.CharField(max_length=255, blank=True)
base_path = models.CharField(max_length=1024, blank=True)
base_snapshot_id = models.CharField(max_length=64, blank=True)
status = models.CharField(max_length=32, blank=True)
started_at = models.DateTimeField(null=True, blank=True)
ended_at = models.DateTimeField(null=True, blank=True)
metadata = models.JSONField(default=dict, blank=True)
discovered_at = models.DateTimeField(auto_now_add=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=["host", "kind", "dirname"], name="unique_snapshot_per_host_kind"),
]
ordering = ["host__host", "-started_at", "dirname"]
def __str__(self) -> str:
return f"{self.host}/{self.kind}/{self.dirname}"
class ScheduleConfig(TimestampedModel):
host = models.OneToOneField(HostConfig, on_delete=models.CASCADE, related_name="schedule")
cron_expr = models.CharField(max_length=128)
user = models.CharField(max_length=64, default="root")
enabled = models.BooleanField(default=True)
prune = models.BooleanField(default=False)
prune_max_delete = models.PositiveIntegerField(default=10)
prune_protect_bases = models.BooleanField(default=False)
last_due_key = models.CharField(max_length=32, blank=True)
last_started_at = models.DateTimeField(null=True, blank=True)
last_finished_at = models.DateTimeField(null=True, blank=True)
last_status = models.CharField(max_length=16, blank=True)
class Meta:
ordering = ["host__host"]
def __str__(self) -> str:
return f"{self.host} {self.cron_expr}"