Enhanced the repository sync
This commit is contained in:
parent
8fb0c1393a
commit
0117713934
4 changed files with 55 additions and 157 deletions
|
|
@ -1,157 +1,66 @@
|
||||||
from pathlib import Path
|
|
||||||
from subprocess import run
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
from .models import BorgRepository
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
logger = getLogger("deployments")
|
logger = getLogger("deployments")
|
||||||
|
|
||||||
# sync users
|
# sync users
|
||||||
|
|
||||||
|
|
||||||
def get_users(user_prefix: str = "u-") -> dict[str, dict[str, Any]]:
|
def sync_repos(dry_run=False):
|
||||||
|
"""Synchronize the repos"""
|
||||||
|
from .models import BorgRepository
|
||||||
|
|
||||||
passwd = Path("/etc/passwd").read_text()
|
repos = BorgRepository.objects.all()
|
||||||
output = dict()
|
print(repos)
|
||||||
for line in passwd.splitlines():
|
|
||||||
name, _, _, _, _, homedir, _ = line.split(":")
|
|
||||||
if not name.startswith(user_prefix):
|
|
||||||
continue
|
|
||||||
|
|
||||||
output[name] = {"homedir": Path(homedir)}
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
def cleanup_users(
|
|
||||||
usernames: set[str],
|
|
||||||
system_users: dict[str, dict[str, Any]],
|
|
||||||
dry_run=False,
|
|
||||||
):
|
|
||||||
"""Cleans up all unused user accounts and directories on the system.
|
|
||||||
|
|
||||||
usernames is the set of users that should exist.
|
|
||||||
system_users contains all the users that exist (with the correct prefix)"""
|
|
||||||
|
|
||||||
base_dir = Path("/home/")
|
|
||||||
|
|
||||||
# cleanup users
|
|
||||||
for user in system_users.keys():
|
|
||||||
if user not in usernames:
|
|
||||||
logger.info("Deleting user '%(user)s'.")
|
|
||||||
print(f"Deleting user '{user}'.")
|
|
||||||
if not dry_run:
|
|
||||||
run(["userdel", user])
|
|
||||||
pass
|
|
||||||
|
|
||||||
# cleanup directories
|
|
||||||
for dir in base_dir.iterdir():
|
|
||||||
if dir.is_dir() and dir.name.startswith("u-") and dir.name not in usernames:
|
|
||||||
logger.info("Deleting home directory '%(dir)s'")
|
|
||||||
print(f"Deleting home directory '{dir}'")
|
|
||||||
if not dry_run:
|
|
||||||
shutil.rmtree(dir)
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def create_users(
|
|
||||||
usernames: set[str],
|
|
||||||
system_users: dict[str, dict[str, Any]],
|
|
||||||
dry_run=False,
|
|
||||||
):
|
|
||||||
|
|
||||||
for username in usernames:
|
|
||||||
if username not in system_users.keys():
|
|
||||||
logger.info("Adding user '%(username)s'")
|
|
||||||
print(f"Adding user '{username}'")
|
|
||||||
if not dry_run:
|
|
||||||
add_user(username)
|
|
||||||
|
|
||||||
|
|
||||||
def add_user(username: str):
|
|
||||||
|
|
||||||
users = get_users()
|
|
||||||
assert username not in users.keys(), "User already exists"
|
|
||||||
|
|
||||||
run(["useradd", "--no-user-group", "--create-home", username], check=True)
|
|
||||||
|
|
||||||
|
|
||||||
def sync_users(dry_run=False):
|
|
||||||
|
|
||||||
# gather data
|
|
||||||
users = User.objects.all()
|
|
||||||
usernames = {f"u-{user.pk}" for user in users}
|
|
||||||
system_users = get_users()
|
|
||||||
|
|
||||||
# clean up old users
|
|
||||||
cleanup_users(usernames=usernames, system_users=system_users, dry_run=dry_run)
|
|
||||||
|
|
||||||
# create the new users
|
|
||||||
create_users(usernames=usernames, system_users=system_users, dry_run=dry_run)
|
|
||||||
|
|
||||||
for user in users:
|
|
||||||
username = f"u-{user.pk}"
|
|
||||||
sync_repos(
|
|
||||||
username=username,
|
|
||||||
user=user,
|
|
||||||
system_user=system_users[username],
|
|
||||||
dry_run=dry_run,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# sync authorized_keys
|
|
||||||
def sync_repos(username: str, user: User, system_user: dict[str, Any], dry_run=False):
|
|
||||||
|
|
||||||
repos = BorgRepository.objects.filter(user=user)
|
|
||||||
|
|
||||||
repos_by_key = defaultdict(list)
|
repos_by_key = defaultdict(list)
|
||||||
for repo in repos:
|
for repo in repos:
|
||||||
repos_by_key[repo.key].append(repo)
|
repos_by_key[repo.key].append(repo)
|
||||||
|
|
||||||
homedir = system_user["homedir"]
|
print(repos)
|
||||||
|
|
||||||
# create .ssh directory
|
# create .ssh directory
|
||||||
ssh_dir = homedir / ".ssh"
|
ssh_dir = settings.BACKUP_AUTHORIZED_KEYS.parent
|
||||||
ssh_dir.mkdir(
|
if not dry_run:
|
||||||
mode=0o750,
|
ssh_dir.mkdir(
|
||||||
exist_ok=True,
|
mode=0o750,
|
||||||
parents=True,
|
exist_ok=True,
|
||||||
)
|
parents=True,
|
||||||
|
|
||||||
# create authorized_keys file
|
|
||||||
authorized_keys = ssh_dir / "authorized_keys"
|
|
||||||
|
|
||||||
commands = []
|
|
||||||
for key, repos in repos_by_key.items():
|
|
||||||
repo_paths = [
|
|
||||||
f"--restrict-to-repository {str(system_user['homedir'] / repo.name)}"
|
|
||||||
for repo in repos
|
|
||||||
]
|
|
||||||
commands.append(
|
|
||||||
f"""command="borg serve {" ".join(repo_paths)} --quota=500G",restrict ssh-rsa {key}"""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
print("\n".join(commands))
|
# create authorized_keys file
|
||||||
|
authorized_keys = settings.BACKUP_AUTHORIZED_KEYS
|
||||||
|
|
||||||
authorized_keys.write_text("\n".join(commands) + "\n")
|
commands = []
|
||||||
|
for key, repositories in repos_by_key.items():
|
||||||
|
repo_paths = [f"--restrict-to-repository {repo.path}" for repo in repositories]
|
||||||
|
commands.append(
|
||||||
|
f"""command="cd {str(settings.BACKUP_BORG_DIR)}; borg serve {" ".join(repo_paths)} --storage-quota=500G",restrict {key}"""
|
||||||
|
)
|
||||||
|
|
||||||
|
if not dry_run:
|
||||||
|
authorized_keys.write_text("\n".join(commands) + "\n")
|
||||||
|
|
||||||
|
print(repos)
|
||||||
# remove repositories that do no longer exist
|
# remove repositories that do no longer exist
|
||||||
repo_names = {repo.name for repo in repos}
|
repo_paths = {repo.path for repo in repos}
|
||||||
print(repo_names)
|
print(repo_paths)
|
||||||
for dir in homedir.iterdir():
|
for user_dir in settings.BACKUP_BORG_DIR.iterdir():
|
||||||
print(dir.name)
|
print(user_dir)
|
||||||
if dir.is_dir() and not dir.name.startswith("."):
|
for dir in user_dir.iterdir():
|
||||||
if dir.name not in repo_names:
|
if dir.is_dir() and not dir.name.startswith("."):
|
||||||
print(f"removing unused repo '{dir}'")
|
if dir not in repo_paths:
|
||||||
shutil.rmtree(dir)
|
print(f"removing unused repo '{dir}'")
|
||||||
|
if not dry_run:
|
||||||
|
shutil.rmtree(dir)
|
||||||
|
|
||||||
# create the repositories
|
# create the repositories
|
||||||
for repo in repos:
|
for repo in repos:
|
||||||
path = system_user["homedir"] / repo.name
|
print(f"creating repo {repo}")
|
||||||
path.mkdir(mode=0o750, exist_ok=True, parents=True)
|
if not dry_run:
|
||||||
shutil.chown(path, user=username)
|
repo.path.mkdir(mode=0o750, exist_ok=True, parents=True)
|
||||||
|
shutil.chown(repo.path, user=settings.BACKUP_USER)
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,9 @@
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand
|
||||||
from ...models import BorgRepository
|
from ...deployments import sync_repos
|
||||||
from ...deployments import sync_users
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Synchronized users on the host with the database."
|
help = "Synchronized users on the host with the database."
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
sync_repos(dry_run=False)
|
||||||
repos = BorgRepository.objects.all()
|
|
||||||
|
|
||||||
for repo in repos:
|
|
||||||
self.stdout.write(f"{repo}")
|
|
||||||
self.stdout.write(self.style.SUCCESS("This is a test"))
|
|
||||||
|
|
||||||
sync_users(dry_run=False)
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
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 pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
from .tasks import update_user
|
||||||
|
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|
@ -34,25 +39,22 @@ class BorgRepository(models.Model):
|
||||||
return f"BorgRepository '{self.user.username}' - '{self.name}' ({self.pk})"
|
return f"BorgRepository '{self.user.username}' - '{self.name}' ({self.pk})"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def username(self) -> str:
|
def path(self) -> Path:
|
||||||
"""Returns the username of the linux user account for this user."""
|
return settings.BACKUP_BORG_DIR / str(self.user.pk) / str(self.pk)
|
||||||
return f"u-{self.pk}"
|
|
||||||
|
|
||||||
@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"{settings.BACKUP_USER}@{settings.BACKUP_REPO_HOST}:{self.user.pk}/{self.pk}"
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
from .tasks import update_user
|
|
||||||
|
|
||||||
update_user.enqueue(user_pk=self.user.pk)
|
|
||||||
super().save()
|
super().save()
|
||||||
|
update_user.enqueue()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
from .tasks import update_user
|
|
||||||
|
|
||||||
super().delete()
|
super().delete()
|
||||||
update_user.enqueue(user_pk=self.user.pk)
|
update_user.enqueue()
|
||||||
|
|
||||||
|
|
||||||
class Voucher(models.Model):
|
class Voucher(models.Model):
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
from django.tasks import task
|
from django.tasks import task
|
||||||
from .deployments import sync_repos, get_users
|
from .deployments import sync_repos
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def update_user(user_pk):
|
def update_user():
|
||||||
user = User.objects.get(pk=user_pk)
|
sync_repos()
|
||||||
username = f"u-{user.pk}"
|
|
||||||
system_user = get_users()[username]
|
|
||||||
|
|
||||||
sync_repos(username=username, user=user, system_user=system_user)
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue