Compare commits
10 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0b69596c8 | |||
| 71e72fb98b | |||
| a942a0b8c6 | |||
| 95fde8e58f | |||
| ea9346d25d | |||
| d0b7158423 | |||
| 528d7b9e4b | |||
| 72fee741e6 | |||
| 68533d9983 | |||
| fb76fffdba |
12 changed files with 157 additions and 9 deletions
38
README.md
38
README.md
|
|
@ -78,6 +78,14 @@ Create a superuser account
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Repository Quota Updates
|
||||||
|
|
||||||
|
The repository quotas in the UI are updated after each backup.
|
||||||
|
To update the quota during an operation the `update_used_quota` management command can be executed on a regular basis.
|
||||||
|
|
||||||
|
There are example configs for systemd-timers in `contrib/communitybackup-quota-update.service` and `contrib/communitybackup-quota-update.timer`. Adjust the paths and the timers to your needs.
|
||||||
|
|
||||||
|
|
||||||
## Custom pages
|
## Custom pages
|
||||||
|
|
||||||
Some pages are specific to your installation. E.g. an imprint.
|
Some pages are specific to your installation. E.g. an imprint.
|
||||||
|
|
@ -138,3 +146,33 @@ Django debug setting.
|
||||||
### BACKUP_MANAGE_PY
|
### 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`
|
`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`
|
||||||
|
|
||||||
|
### ADDITIONAL_FOOTER_NAV_ITEMS
|
||||||
|
|
||||||
|
`ADDITIONAL_FOOTER_NAV_ITEMS` allows you to add additional links to the footer. This is a `list[str]`. Each item gets rendered as is into a `<li class=nav-item>`. So usually you want to continue the proper Bootstrap styling in the lst. E.g.:
|
||||||
|
|
||||||
|
```python
|
||||||
|
ADDITIONAL_FOOTER_NAV_ITEMS = ["""<a class="nav-link" href="https://example.com/>Example Link</a>""",]
|
||||||
|
```
|
||||||
|
|
||||||
|
### BORG_SERVER_PUBKEYS
|
||||||
|
|
||||||
|
A list of the SSH public keys and their hashes to verify the server a User is connecting to.
|
||||||
|
This is a list of tuples, containing the key and it's hash.
|
||||||
|
|
||||||
|
```python
|
||||||
|
BORG_SERVER_PUBKEYS = [
|
||||||
|
(
|
||||||
|
"ecdsa-sha2-nistp256 AAAAASDJIASKJDASD root@example.com",
|
||||||
|
"256 SHA256:sTbOK9NvP1uUEixgUT8KUiYrY8J/DbK+jR39lwcT8Zw root@example.com (ECDSA)",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ssh-ed25519 AAAAasdiwdkjasdijwklajsdijasd root@example.com",
|
||||||
|
"256 SHA256:hPbWwRxNr1mFHZKYjcysnay1cQGQsOmDBvkA3Pzo4YY root@example.com (ED25519)",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ssh-rsa AAAABasdlkjasdiualksjd root@example.com",
|
||||||
|
"3072 SHA256:deuPTR8Hcc1LP7DHqAp91EINdLBQoco2IeMldIahamQ root@example.com (RSA)",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,15 @@ def release_version(_request):
|
||||||
return {
|
return {
|
||||||
"RELEASE_VERSION": settings.RELEASE_VERSION,
|
"RELEASE_VERSION": settings.RELEASE_VERSION,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def additional_footer_nav_items(_request):
|
||||||
|
return {
|
||||||
|
"ADDITIONAL_FOOTER_NAV_ITEMS": settings.ADDITIONAL_FOOTER_NAV_ITEMS,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def borg_server_pubkeys(_request):
|
||||||
|
return {
|
||||||
|
"BORG_SERVER_PUBKEYS": settings.BORG_SERVER_PUBKEYS,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,18 @@ SECRET_KEY = "change me!"
|
||||||
|
|
||||||
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")
|
BACKUP_MANAGE_PY = Path("/path/to/venv/bin/python /path/to/community_backup/manage.py")
|
||||||
|
|
||||||
|
# BORG_SERVER_PUBKEYS = [
|
||||||
|
# (
|
||||||
|
# "ecdsa-sha2-nistp256 AAAAASDJIASKJDASD root@example.com",
|
||||||
|
# "256 SHA256:sTbOK9NvP1uUEixgUT8KUiYrY8J/DbK+jR39lwcT8Zw root@example.com (ECDSA)",
|
||||||
|
# ),
|
||||||
|
# (
|
||||||
|
# "ssh-ed25519 AAAAasdiwdkjasdijwklajsdijasd root@example.com",
|
||||||
|
# "256 SHA256:hPbWwRxNr1mFHZKYjcysnay1cQGQsOmDBvkA3Pzo4YY root@example.com (ED25519)",
|
||||||
|
# ),
|
||||||
|
# (
|
||||||
|
# "ssh-rsa AAAABasdlkjasdiualksjd root@example.com",
|
||||||
|
# "3072 SHA256:deuPTR8Hcc1LP7DHqAp91EINdLBQoco2IeMldIahamQ root@example.com (RSA)",
|
||||||
|
# ),
|
||||||
|
# ]
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@ TEMPLATES = [
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
"community_backup.context_processors.release_version",
|
"community_backup.context_processors.release_version",
|
||||||
|
"community_backup.context_processors.additional_footer_nav_items",
|
||||||
|
"community_backup.context_processors.borg_server_pubkeys",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -115,7 +117,7 @@ TASKS = {"default": {"BACKEND": "django.tasks.backends.immediate.ImmediateBacken
|
||||||
|
|
||||||
STATIC_ROOT = BASE_DIR.parent.parent / "static"
|
STATIC_ROOT = BASE_DIR.parent.parent / "static"
|
||||||
|
|
||||||
RELEASE_VERSION = "0.8"
|
RELEASE_VERSION = "0.12"
|
||||||
|
|
||||||
# Import settings from configuration.py
|
# Import settings from configuration.py
|
||||||
try:
|
try:
|
||||||
|
|
@ -148,3 +150,5 @@ MARKDOWN_PAGE_DIR = module.MARKDOWN_PAGE_DIR
|
||||||
BACKUP_MANAGE_PY = module.BACKUP_MANAGE_PY
|
BACKUP_MANAGE_PY = module.BACKUP_MANAGE_PY
|
||||||
|
|
||||||
DEBUG = getattr(module, "DEBUG", False)
|
DEBUG = getattr(module, "DEBUG", False)
|
||||||
|
ADDITIONAL_FOOTER_NAV_ITEMS = getattr(module, "ADDITIONAL_FOOTER_NAV_ITEMS", list())
|
||||||
|
BORG_SERVER_PUBKEYS = getattr(module, "BORG_SERVER_PUBKEYS", list())
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Update the used quota
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=borg
|
||||||
|
Group=borg
|
||||||
|
WorkingDirectory=/opt/community_backup/community-backup/community_backup/
|
||||||
|
ExecStart=/opt/community_backup/venv/bin/python manage.py update_used_quota
|
||||||
|
PrivateTmp=true
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Update the used quota on a regular basis
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* *:0/5:00
|
||||||
|
RandomizedDelaySec=120
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
39
community_backup/webui/management/commands/add_vouchers.py
Normal file
39
community_backup/webui/management/commands/add_vouchers.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from random import choices
|
||||||
|
from ...models import Voucher
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "bulk-add vouchers with a given prefix and generate a latex output to render them"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument("prefix", type=str)
|
||||||
|
parser.add_argument("amount", type=int)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
unabmigous_characters = "abcdefghijkmnopqrstuvwxyzACDEFHJKLMNPQRTUVWXY1234679"
|
||||||
|
|
||||||
|
vouchers = set()
|
||||||
|
while len(vouchers) < options["amount"]:
|
||||||
|
random_part = "".join(choices(unabmigous_characters, k=5))
|
||||||
|
vouchers.add(f"{options['prefix']}-{random_part}")
|
||||||
|
|
||||||
|
Voucher.objects.bulk_create(Voucher(code=v) for v in vouchers)
|
||||||
|
|
||||||
|
header = r"""\begin{document}
|
||||||
|
\begin{center}
|
||||||
|
"""
|
||||||
|
output = header
|
||||||
|
even = False
|
||||||
|
for v in vouchers:
|
||||||
|
output += f"\\voucher{{{v}}}"
|
||||||
|
if even:
|
||||||
|
output += "\\newline\n"
|
||||||
|
else:
|
||||||
|
output += "\n"
|
||||||
|
even = not even
|
||||||
|
|
||||||
|
output += r"""\end{center}
|
||||||
|
\end{document}"""
|
||||||
|
|
||||||
|
print(output)
|
||||||
|
|
@ -10,8 +10,6 @@ class Command(BaseCommand):
|
||||||
parser.add_argument("--repository-id", type=int, nargs="+")
|
parser.add_argument("--repository-id", type=int, nargs="+")
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
print(options)
|
|
||||||
|
|
||||||
qs = BorgRepository.objects.all()
|
qs = BorgRepository.objects.all()
|
||||||
|
|
||||||
if options.get("user_id"):
|
if options.get("user_id"):
|
||||||
|
|
@ -20,8 +18,6 @@ class Command(BaseCommand):
|
||||||
if options.get("repository_id"):
|
if options.get("repository_id"):
|
||||||
qs = qs.filter(pk__in=options.get("repository_id"))
|
qs = qs.filter(pk__in=options.get("repository_id"))
|
||||||
|
|
||||||
print(qs)
|
|
||||||
|
|
||||||
for repo in qs:
|
for repo in qs:
|
||||||
repo.refresh_quota()
|
repo.refresh_quota()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ class BorgRepository(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def repo_url(self) -> str:
|
def repo_url(self) -> str:
|
||||||
return f"{settings.BACKUP_USER}@{settings.BACKUP_REPO_HOST}:{self.user.pk}/{self.pk}"
|
return f"ssh://{settings.BACKUP_USER}@{settings.BACKUP_REPO_HOST}/./{self.user.pk}/{self.pk}"
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,17 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{% if ADDITIONAL_FOOTER_NAV_ITEMS %}
|
||||||
|
<div class="col-3">
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
{% for item in ADDITIONAL_FOOTER_NAV_ITEMS %}
|
||||||
|
<li class="nav-item">
|
||||||
|
{{item|safe}}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@
|
||||||
{% for repo in object_list %}
|
{% for repo in object_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ repo.name }}</td>
|
<td>{{ repo.name }}</td>
|
||||||
<td><code>{{ repo.truncated_key }}</code></td>
|
<td style="word-break: break-all"><code>{{ repo.truncated_key }}</code></td>
|
||||||
<td><code>{{ repo.repo_url }}</code></td>
|
<td style="word-break: break-all"><code>{{ repo.repo_url }}</code></td>
|
||||||
<td>{% if repo.used_quota < 0 %}not initialized{% else %}{{ repo.used_quota }}/{{ repo.quota }} GB{% endif %}</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>
|
||||||
|
|
@ -35,6 +35,21 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{% if BORG_SERVER_PUBKEYS %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h4>Borg Server Public Keys</h4>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
{% for key, fingerprint in BORG_SERVER_PUBKEYS %}
|
||||||
|
<li class="list-group-item">
|
||||||
|
<span style="word-break: break-all"><code>{{ key }}</code></span><br>
|
||||||
|
<span style="word-break: break-all"><code>{{ fingerprint }}</code></span>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "community_backup"
|
name = "community_backup"
|
||||||
version = "0.8"
|
version = "0.12"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue