diff --git a/.gitignore b/.gitignore index 7db414f..6d35582 100644 --- a/.gitignore +++ b/.gitignore @@ -207,3 +207,5 @@ tags [._]*.un~ # End of https://www.toptal.com/developers/gitignore/api/vim,venv,python + +community_backup/community_backup/configuration.py diff --git a/README.md b/README.md index 995a282..f6af251 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,111 @@ A website to manage backup repositories. ## Installation -TODO +This installation guide assumes a Debian 13 system. + +Install Python and several dependencies: + +This example should wo + +```bash +apt update && apt install python3 python3-pip python3-venv borgbackup +``` + +Clone the repository + +git clone https://git.srvspace.net/jo/community-backup.git + +Or install from a release + +Create a venv + +```bash +python3 -m venv venv +``` + +Activea the venv + +```bash +source venv/bin/activate +``` + +Install the package and it's dependencies: + +```bash +pip install -e community-backup +``` + +Create a configuration file in `community_backup/community_backup/community_backup/configration.py`, e.g. by copying and adjusting the `example_configuraton.py` next to that location. + +The settings are explained in detail in the setting section. + +Apply the migrations: + +```bash +python community-backup/community_backup/manage.py migrate +``` + +Collect the static files: + +```bash +# python community-backup/community_backup/manage.py collectstatic + +130 static files copied to '/opt/community_backup/static'. +``` + +Point your webserver to the static files. E.g. with caddy: + +``` +example.backups.org { + handle /static/* { + file_server { + root /opt/community_backup/ + } + } + + handle { + reverse_proxy http://localhost:8000 + } +} +``` + +Create a superuser account + +``` + +``` + +## Settings + +### BACKUP_USER + +`BACKUP_USER` specifies the username, that is used for borg backups. This user is used for various file permissions as well as the user that clients are using to log in and push their backups. + +### BACKUP_REPO_HOST + +`BACKUP_REPO_HOST` is the hostname given to the user for pushing their backups to. E.g. `backup.example.com`. + +### BACKUP_HOME_DIR + +`BACKUP_HOME_DIR` is the home directory of the borg user. This must be a `pathlib.Path`. + +### BACKUP_BORG_DIR + +`BACKUP_BORG_DIR` is the directory in which the actual backups are stored. This must be a `pathlib.Path` + +### BACKUP_AUTHORIZED_KEYS + +`BACKUP_AUTHORIZED_KEYS` is the authorized_keys file of the SSH daemon used for `BORG_USER`. This must be a `pathlib.Path` + +### DATABASES + +`DATABASES` is the Django database setting. Here is an example for sqlite3. + +``` +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": "/some/path/to/db.sqlite3", + } +} +``` diff --git a/community_backup/community_backup/configuration.py b/community_backup/community_backup/configuration.py deleted file mode 100644 index ea8353c..0000000 --- a/community_backup/community_backup/configuration.py +++ /dev/null @@ -1,2 +0,0 @@ -# enter your overrides here -print("foo") diff --git a/community_backup/community_backup/example_configuration.py b/community_backup/community_backup/example_configuration.py new file mode 100644 index 0000000..fec60ea --- /dev/null +++ b/community_backup/community_backup/example_configuration.py @@ -0,0 +1,15 @@ +# enter your overrides here +from pathlib import Path + +BACKUP_HOME_DIR = Path("/data/backups") +BACKUP_BORG_DIR = BACKUP_HOME_DIR / "borg" +BACKUP_AUTHORIZED_KEYS = BACKUP_HOME_DIR / ".ssh" / "authorized_keys" +BACKUP_USER = "borg" +BACKUP_REPO_HOST = "backup.example.com" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": "/path/to/the/db.sqlite3", + } +} diff --git a/community_backup/community_backup/settings.py b/community_backup/community_backup/settings.py index 75ca1de..47514da 100644 --- a/community_backup/community_backup/settings.py +++ b/community_backup/community_backup/settings.py @@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/6.0/ref/settings/ """ from pathlib import Path +from importlib import import_module # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -78,17 +79,6 @@ TEMPLATES = [ WSGI_APPLICATION = "community_backup.wsgi.application" -# Database -# https://docs.djangoproject.com/en/6.0/ref/settings/#databases - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", - } -} - - # Password validation # https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators @@ -127,4 +117,28 @@ STATIC_URL = "static/" TASKS = {"default": {"BACKEND": "django.tasks.backends.immediate.ImmediateBackend"}} -from .configuration import * +STATIC_ROOT = BASE_DIR.parent.parent / "static" + +# Import settings from configuration.py +try: + config_module = "community_backup.configuration" + module = import_module(config_module) + + assert module.BACKUP_USER, "The BACKUP_USER setting is required." + assert module.BACKUP_REPO_HOST, "The BACKUP_REPO_HOST setting is required." + assert module.BACKUP_HOME_DIR, "The BACKUP_HOME_DIR setting is required." + assert module.BACKUP_BORG_DIR, "The BACKUP_BORG_DIR setting is required." + assert module.BACKUP_AUTHORIZED_KEYS, ( + "The BACKUP_AUTHORIZED_KEYS setting is required." + ) + +except ModuleNotFoundError: + print(f"could not find configuration file {config_module}") + exit(1) + +BACKUP_USER = module.BACKUP_USER +BACKUP_REPO_HOST = module.BACKUP_REPO_HOST +BACKUP_HOME_DIR = module.BACKUP_HOME_DIR +BACKUP_BORG_DIR = module.BACKUP_BORG_DIR +BACKUP_AUTHORIZED_KEYS = module.BACKUP_AUTHORIZED_KEYS +DATABASES = module.DATABASES diff --git a/community_backup/webui/deployments.py b/community_backup/webui/deployments.py index 0f1c9bd..b56d1bc 100644 --- a/community_backup/webui/deployments.py +++ b/community_backup/webui/deployments.py @@ -16,13 +16,11 @@ def sync_repos(dry_run=False): from .models import BorgRepository repos = BorgRepository.objects.all() - print(repos) repos_by_key = defaultdict(list) for repo in repos: repos_by_key[repo.key].append(repo) - print(repos) # create .ssh directory ssh_dir = settings.BACKUP_AUTHORIZED_KEYS.parent if not dry_run: @@ -45,10 +43,8 @@ def sync_repos(dry_run=False): if not dry_run: authorized_keys.write_text("\n".join(commands) + "\n") - print(repos) # remove repositories that do no longer exist repo_paths = {repo.path for repo in repos} - print(repo_paths) for user_dir in settings.BACKUP_BORG_DIR.iterdir(): print(user_dir) for dir in user_dir.iterdir(): diff --git a/community_backup/webui/forms.py b/community_backup/webui/forms.py index de62d80..99e774c 100644 --- a/community_backup/webui/forms.py +++ b/community_backup/webui/forms.py @@ -15,7 +15,7 @@ class BorgRepositoryForm(forms.ModelForm): class RegisterUserForm(UserCreationForm): email = forms.EmailField() - voucher = forms.CharField(help_text="You registration voucher.") + voucher = forms.CharField(help_text="Your registration voucher.") def clean_voucher(self): obj = Voucher.objects.filter(code=self.cleaned_data["voucher"], used=False) diff --git a/community_backup/webui/migrations/0004_alter_borgrepository_key_alter_borgrepository_name.py b/community_backup/webui/migrations/0004_alter_borgrepository_key_alter_borgrepository_name.py new file mode 100644 index 0000000..b794790 --- /dev/null +++ b/community_backup/webui/migrations/0004_alter_borgrepository_key_alter_borgrepository_name.py @@ -0,0 +1,39 @@ +# Generated by Django 6.0.3 on 2026-04-06 19:51 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("webui", "0003_voucher"), + ] + + operations = [ + migrations.AlterField( + model_name="borgrepository", + name="key", + field=models.TextField( + validators=[ + django.core.validators.RegexValidator( + "(ssh\\-rsa|ecdsa\\-sha2\\-nistp256|ssh\\-ed25519) ([a-zA-Z0-9\\+/=]+) (\\S*)", + message="not a valid SSH public key.", + ) + ] + ), + ), + migrations.AlterField( + model_name="borgrepository", + name="name", + field=models.CharField( + max_length=100, + validators=[ + django.core.validators.RegexValidator( + "[a-zA-Z0-9\\-_]+", + message="Only a-z, A-Z, 0-9, - and _ are allowed.", + ) + ], + ), + ), + ] diff --git a/community_backup/webui/templates/base.html b/community_backup/webui/templates/base.html index 30bc23c..ffa2b06 100644 --- a/community_backup/webui/templates/base.html +++ b/community_backup/webui/templates/base.html @@ -48,13 +48,7 @@
diff --git a/community_backup/webui/templates/borg_list.html b/community_backup/webui/templates/borg_list.html index ae3b941..b193fcd 100644 --- a/community_backup/webui/templates/borg_list.html +++ b/community_backup/webui/templates/borg_list.html @@ -1,13 +1,17 @@ {% extends "base.html" %} {% block content %} -
-

Your Borg Repositories

+
+

Your Borg Repositories

+ +

Create a borg repository by specifying a name and the SSH public key that is allowed to access this repository. The name is only for your convenience. To set up the repository please follow the BorgBackup documentation. There is also a Quick start guide available. + Always use encrypted backups!

+
@@ -30,4 +34,6 @@
+ + {% endblock %} diff --git a/community_backup/webui/templates/landing.html b/community_backup/webui/templates/landing.html index 1277742..39db761 100644 --- a/community_backup/webui/templates/landing.html +++ b/community_backup/webui/templates/landing.html @@ -4,4 +4,10 @@

Welcome to Community Backup!

+ + +

This is a service offering space for backups free of charge. This is a hobby project. It comes with no garantuees for anything, especially availability of the service. This service is in a testing phase at the moment. It might be unavailalbe at times and features might change quickly.

+

Currently this service offers only backups via Borg Backup.

+

To access this service you need an account. To register for an account you need a voucher. Vouchers are required to control the amount of users, so that there is enough space available for everyone.

+
{% endblock %} diff --git a/community_backup/webui/urls.py b/community_backup/webui/urls.py index 1476988..0bcf271 100644 --- a/community_backup/webui/urls.py +++ b/community_backup/webui/urls.py @@ -17,5 +17,5 @@ urlpatterns = [ name="borg_delete", ), path("register/", views.RegisterUserView.as_view(), name="register"), - path("about/", views.AboutView.as_view(), name="about"), + path("imprint/", views.ImprintView.as_view(), name="imprint"), ] diff --git a/community_backup/webui/views.py b/community_backup/webui/views.py index a491a73..f265477 100644 --- a/community_backup/webui/views.py +++ b/community_backup/webui/views.py @@ -41,9 +41,14 @@ class MarkdownView(TemplateView): return context -class AboutView(MarkdownView): +class DataProtectionView(MarkdownView): template_name = "markdown.html" - md = "about.md" + md = "dataprotection.md" + + +class ImprintView(MarkdownView): + template_name = "markdown.html" + md = "imprint.md" class BorgView(LoginRequiredMixin, ListView): diff --git a/pyproject.toml b/pyproject.toml index e221116..412eb3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,5 +8,6 @@ dependencies = [ "crispy-bootstrap5>=2026.3", "django>=6.0.3", "django-crispy-forms>=2.6", + "gunicorn>=25.3.0", "markdown2>=2.5.5", ]