Added Repository Quota setting
This commit is contained in:
parent
755e313c61
commit
3a4f5a0394
10 changed files with 102 additions and 2 deletions
|
|
@ -134,3 +134,7 @@ Django debug setting.
|
||||||
### MARKDOWN_PAGE_DIR
|
### MARKDOWN_PAGE_DIR
|
||||||
|
|
||||||
`MARKDOWN_PAGE_DIR` is the directory where customized markdown files are put. This directory is required.
|
`MARKDOWN_PAGE_DIR` is the directory where customized markdown files are put. This directory is required.
|
||||||
|
|
||||||
|
### BACKUP_MANAGE_PY
|
||||||
|
|
||||||
|
`BACKUP_MANAGE_PY` is the command to run the `manage.py` file, including e.g. a Python interpreter, venv, etc. This must be a `pathlib.Path`
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,4 @@ SECRET_KEY = "change me!"
|
||||||
# DEBUG = True
|
# DEBUG = True
|
||||||
|
|
||||||
MARKDOWN_PAGE_DIR = Path("./custom_md/")
|
MARKDOWN_PAGE_DIR = Path("./custom_md/")
|
||||||
|
BACKUP_MANAGE_PY = Path("/path/to/venv/bin/python /path/to/community_backup/manage.py")
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ try:
|
||||||
"The BACKUP_AUTHORIZED_KEYS setting is required."
|
"The BACKUP_AUTHORIZED_KEYS setting is required."
|
||||||
)
|
)
|
||||||
assert module.MARKDOWN_PAGE_DIR, "The MARKDOWN_PAGE_DIR setting is required."
|
assert module.MARKDOWN_PAGE_DIR, "The MARKDOWN_PAGE_DIR setting is required."
|
||||||
|
assert module.BACKUP_MANAGE_PY, "The BACKUP_MANAGE_PY setting is required"
|
||||||
|
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
print(f"could not find configuration file {config_module}")
|
print(f"could not find configuration file {config_module}")
|
||||||
|
|
@ -144,5 +145,6 @@ BACKUP_AUTHORIZED_KEYS = module.BACKUP_AUTHORIZED_KEYS
|
||||||
DATABASES = module.DATABASES
|
DATABASES = module.DATABASES
|
||||||
SECRET_KEY = module.SECRET_KEY
|
SECRET_KEY = module.SECRET_KEY
|
||||||
MARKDOWN_PAGE_DIR = module.MARKDOWN_PAGE_DIR
|
MARKDOWN_PAGE_DIR = module.MARKDOWN_PAGE_DIR
|
||||||
|
BACKUP_MANAGE_PY = module.BACKUP_MANAGE_PY
|
||||||
|
|
||||||
DEBUG = getattr(module, "DEBUG", False)
|
DEBUG = getattr(module, "DEBUG", False)
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,10 @@ def sync_repos(dry_run=False):
|
||||||
commands = []
|
commands = []
|
||||||
for key, repositories in repos_by_key.items():
|
for key, repositories in repos_by_key.items():
|
||||||
repo_paths = [f"--restrict-to-repository {repo.path}" for repo in repositories]
|
repo_paths = [f"--restrict-to-repository {repo.path}" for repo in repositories]
|
||||||
|
quota = max(repo.quota for repo in repositories)
|
||||||
|
ids = [str(repo.id) for repo in repositories]
|
||||||
commands.append(
|
commands.append(
|
||||||
f"""command="cd {str(settings.BACKUP_BORG_DIR)}; borg serve {" ".join(repo_paths)} --storage-quota=500G",restrict {key}"""
|
f"""command="cd {str(settings.BACKUP_BORG_DIR)}; borg serve {" ".join(repo_paths)} --storage-quota={quota}G;{settings.BACKUP_MANAGE_PY} update_used_quota --repository-id {" ".join(ids)}",restrict {key}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from ...models import BorgRepository
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Update the used quota values"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument("--user-id", type=int)
|
||||||
|
parser.add_argument("--repository-id", type=int, nargs="+")
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
print(options)
|
||||||
|
|
||||||
|
qs = BorgRepository.objects.all()
|
||||||
|
|
||||||
|
if options.get("user_id"):
|
||||||
|
qs = qs.filter(user__pk=options.get("user_id"))
|
||||||
|
|
||||||
|
if options.get("repository_id"):
|
||||||
|
qs = qs.filter(pk__in=options.get("repository_id"))
|
||||||
|
|
||||||
|
print(qs)
|
||||||
|
|
||||||
|
for repo in qs:
|
||||||
|
repo.refresh_quota()
|
||||||
|
|
||||||
|
BorgRepository.objects.bulk_update(qs, ["used_quota"])
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 6.0.3 on 2026-04-20 15:36
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("webui", "0005_alter_borgrepository_key"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="borgrepository",
|
||||||
|
name="quota",
|
||||||
|
field=models.IntegerField(default=500),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="borgrepository",
|
||||||
|
name="used_quota",
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 6.0.3 on 2026-04-20 17:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("webui", "0006_borgrepository_quota_borgrepository_used_quota"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="borgrepository",
|
||||||
|
name="used_quota",
|
||||||
|
field=models.IntegerField(default=-1),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -3,6 +3,7 @@ from django.contrib.auth.models import User
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import msgpack
|
||||||
|
|
||||||
|
|
||||||
from .tasks import update_user
|
from .tasks import update_user
|
||||||
|
|
@ -28,6 +29,9 @@ class BorgRepository(models.Model):
|
||||||
)
|
)
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
quota = models.IntegerField(default=500)
|
||||||
|
used_quota = models.IntegerField(default=-1)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
|
|
@ -95,6 +99,21 @@ class BorgRepository(models.Model):
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def refresh_quota(self):
|
||||||
|
hints_files = self.path.glob("hints.*")
|
||||||
|
|
||||||
|
try:
|
||||||
|
hint = next(hints_files)
|
||||||
|
data = hint.open(mode="rb").read()
|
||||||
|
|
||||||
|
unpacked = msgpack.unpackb(data, strict_map_key=False, raw=True)
|
||||||
|
quota_used = unpacked[b"storage_quota_use"]
|
||||||
|
|
||||||
|
self.used_quota = int(quota_used / 10**9)
|
||||||
|
except StopIteration:
|
||||||
|
# No hints file found, therefore the repo is probably not initialized
|
||||||
|
self.used_quota = -1
|
||||||
|
|
||||||
|
|
||||||
class Voucher(models.Model):
|
class Voucher(models.Model):
|
||||||
used = models.BooleanField(default=False)
|
used = models.BooleanField(default=False)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Key</th>
|
<th>Key</th>
|
||||||
<th>Repo-URL</th>
|
<th>Repo-URL</th>
|
||||||
|
<th>Quota (used/total)</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -26,6 +27,7 @@
|
||||||
<td>{{ repo.name }}</td>
|
<td>{{ repo.name }}</td>
|
||||||
<td><code>{{ repo.truncated_key }}</code></td>
|
<td><code>{{ repo.truncated_key }}</code></td>
|
||||||
<td><code>{{ repo.repo_url }}</code></td>
|
<td><code>{{ repo.repo_url }}</code></td>
|
||||||
|
<td>{% if repo.used_quota < 0 %}not initialized{% else %}{{ repo.used_quota }}/{{ repo.quota }} GB{% endif %}</td>
|
||||||
<td><a href="{% url 'borg_update' pk=repo.pk %}" class="btn btn-warning btn-sm">Edit</a>
|
<td><a href="{% url 'borg_update' pk=repo.pk %}" class="btn btn-warning btn-sm">Edit</a>
|
||||||
<a href="{% url 'borg_delete' pk=repo.pk %}" class="btn btn-danger btn-sm">Delete</a></td>
|
<a href="{% url 'borg_delete' pk=repo.pk %}" class="btn btn-danger btn-sm">Delete</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,5 @@ dependencies = [
|
||||||
"django-crispy-forms>=2.6",
|
"django-crispy-forms>=2.6",
|
||||||
"gunicorn>=25.3.0",
|
"gunicorn>=25.3.0",
|
||||||
"markdown2>=2.5.5",
|
"markdown2>=2.5.5",
|
||||||
|
"msgpack>=1.1.2",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue