Merge remote-tracking branch 'upstream/restic-rewrite-api' into restic-rewrite

pull/35/head
Houkime 2023-06-14 10:27:50 +00:00
commit 8554879dc2
9 changed files with 56 additions and 28 deletions

View File

@ -15,13 +15,13 @@ redis = RedisPool().get_connection()
class LocalBackupSecret:
@staticmethod
def get():
def get() -> str:
"""A secret string which backblaze/other clouds do not know.
Serves as encryption key.
"""
if not LocalBackupSecret.exists():
LocalBackupSecret.reset()
return redis.get(REDIS_KEY)
return redis.get(REDIS_KEY) # type: ignore
@staticmethod
def set(secret: str):
@ -38,7 +38,7 @@ class LocalBackupSecret:
@staticmethod
def exists() -> bool:
return redis.exists(REDIS_KEY)
return redis.exists(REDIS_KEY) == 1
@staticmethod
def _generate() -> str:

View File

@ -4,3 +4,5 @@ from selfprivacy_api.backup.restic_backuper import ResticBackuper
class Backblaze(AbstractBackupProvider):
backuper = ResticBackuper("--b2-account", "--b2-key", ":b2:")
name = "BACKBLAZE"

View File

@ -5,6 +5,8 @@ from selfprivacy_api.backup.restic_backuper import ResticBackuper
class LocalFileBackup(AbstractBackupProvider):
backuper = ResticBackuper("", "", "memory")
name = "FILE"
# login and key args are for compatibility with generic provider methods. They are ignored.
def __init__(self, filename: str, login: str = "", key: str = ""):
super().__init__()

View File

@ -4,3 +4,5 @@ from selfprivacy_api.backup.restic_backuper import ResticBackuper
class InMemoryBackup(AbstractBackupProvider):
backuper = ResticBackuper("", "", ":memory:")
name = "MEMORY"

View File

@ -12,6 +12,8 @@ class AbstractBackupProvider(ABC):
def backuper(self) -> AbstractBackuper:
raise NotImplementedError
name = "NONE"
def __init__(self, login="", key="", location="", repo_id=""):
self.backuper.set_creds(login, key, location)
self.login = login

View File

@ -1,9 +0,0 @@
import datetime
import strawberry
@strawberry.type
class SnapshotInfo:
id: str
service_name: str
created_at: datetime.datetime

View File

@ -3,7 +3,6 @@ import typing
import strawberry
import datetime
from selfprivacy_api.graphql.common_types.dns import DnsRecord
from selfprivacy_api.graphql.common_types.backup_snapshot import SnapshotInfo
from selfprivacy_api.services import get_service_by_id, get_services_by_location
from selfprivacy_api.services import Service as ServiceInterface
@ -104,14 +103,14 @@ class Service:
return get_storage_usage(self)
@strawberry.field
def backup_snapshots(self) -> typing.Optional[typing.List[SnapshotInfo]]:
def backup_snapshots(self) -> typing.Optional[typing.List["SnapshotInfo"]]:
return None
@strawberry.type
class SnapshotInfo:
id: str
service: "Service"
service: Service
created_at: datetime.datetime

View File

@ -7,41 +7,70 @@ import strawberry
from selfprivacy_api.backup import Backups
from selfprivacy_api.backup.local_secret import LocalBackupSecret
from selfprivacy_api.graphql.queries.providers import BackupProvider
from selfprivacy_api.graphql.common_types.service import SnapshotInfo
from selfprivacy_api.graphql.common_types.service import (
Service,
ServiceStatusEnum,
SnapshotInfo,
service_to_graphql_service,
)
from selfprivacy_api.services import get_service_by_id
@strawberry.type
class BackupConfiguration:
provider: BackupProvider
# When server is lost, the app should have the key to decrypt backups on a new server
# When server is lost, the app should have the key to decrypt backups
# on a new server
encryption_key: str
# False when repo is not initialized and not ready to be used
is_initialized: bool
# If none, autobackups are disabled
autobackup_period: typing.Optional[int] = None
autobackup_period: typing.Optional[int]
# Bucket name for Backblaze, path for some other providers
location_name: typing.Optional[str] = None
location_id: typing.Optional[str] = None
location_name: typing.Optional[str]
location_id: typing.Optional[str]
@strawberry.type
class Backup:
@strawberry.field
def configuration() -> BackupConfiguration:
config = BackupConfiguration()
config.encryption_key = LocalBackupSecret.get()
config.is_initialized = Backups.is_initted()
config.autobackup_period = Backups.autobackup_period_minutes()
config.location_name = Backups.provider().location
config.location_id = Backups.provider().repo_id
def configuration(self) -> BackupConfiguration:
return BackupConfiguration(
provider=BackupProvider[Backups.provider().name],
encryption_key=LocalBackupSecret.get(),
is_initialized=Backups.is_initted(),
autobackup_period=Backups.autobackup_period_minutes(),
location_name=Backups.provider().location,
location_id=Backups.provider().repo_id,
)
@strawberry.field
def all_snapshots(self) -> typing.List[SnapshotInfo]:
if not Backups.is_initted():
return []
result = []
snapshots = Backups.get_all_snapshots()
for snap in snapshots:
service = get_service_by_id(snap.service_name)
if service is None:
service = Service(
id=snap.service_name,
display_name=f"{snap.service_name} (Orphaned)",
description="",
svg_icon="",
is_movable=False,
is_required=False,
is_enabled=False,
status=ServiceStatusEnum.OFF,
url=None,
dns_records=None,
)
else:
service = service_to_graphql_service(service)
graphql_snap = SnapshotInfo(
id=snap.id, service=snap.service_name, created_at=snap.created_at
id=snap.id,
service=service,
created_at=snap.created_at,
)
result.append(graphql_snap)
return result

View File

@ -19,6 +19,7 @@ class ServerProvider(Enum):
@strawberry.enum
class BackupProvider(Enum):
BACKBLAZE = "BACKBLAZE"
NONE = "NONE"
# for testing purposes, make sure not selectable in prod.
MEMORY = "MEMORY"
FILE = "FILE"