2022-08-25 17:03:56 +00:00
|
|
|
"""Services module."""
|
|
|
|
|
2024-10-23 11:38:01 +00:00
|
|
|
import logging
|
2024-07-24 15:15:31 +00:00
|
|
|
import base64
|
2022-08-25 17:03:56 +00:00
|
|
|
import typing
|
2024-07-24 15:15:31 +00:00
|
|
|
from typing import List
|
2024-09-11 10:15:32 +00:00
|
|
|
from os import path, remove
|
2024-08-21 13:55:30 +00:00
|
|
|
from os import makedirs
|
|
|
|
from os import listdir
|
2024-07-25 17:30:46 +00:00
|
|
|
from os.path import join
|
2024-07-24 15:15:31 +00:00
|
|
|
|
2024-10-14 11:29:00 +00:00
|
|
|
from shutil import copyfile, copytree, rmtree
|
2022-08-25 17:03:56 +00:00
|
|
|
from selfprivacy_api.services.bitwarden import Bitwarden
|
2024-06-30 19:02:07 +00:00
|
|
|
from selfprivacy_api.services.forgejo import Forgejo
|
2024-01-09 18:58:09 +00:00
|
|
|
from selfprivacy_api.services.jitsimeet import JitsiMeet
|
2024-07-30 13:55:57 +00:00
|
|
|
from selfprivacy_api.services.prometheus import Prometheus
|
2024-06-06 19:14:07 +00:00
|
|
|
from selfprivacy_api.services.roundcube import Roundcube
|
2022-08-25 17:03:56 +00:00
|
|
|
from selfprivacy_api.services.mailserver import MailServer
|
|
|
|
from selfprivacy_api.services.nextcloud import Nextcloud
|
|
|
|
from selfprivacy_api.services.pleroma import Pleroma
|
|
|
|
from selfprivacy_api.services.ocserv import Ocserv
|
2024-07-24 15:15:31 +00:00
|
|
|
|
2022-08-25 17:03:56 +00:00
|
|
|
from selfprivacy_api.services.service import Service, ServiceDnsRecord
|
2024-07-24 15:15:31 +00:00
|
|
|
from selfprivacy_api.services.service import ServiceStatus
|
2022-08-25 17:03:56 +00:00
|
|
|
import selfprivacy_api.utils.network as network_utils
|
|
|
|
|
2024-09-11 10:15:32 +00:00
|
|
|
from selfprivacy_api.services.api_icon import API_ICON
|
|
|
|
from selfprivacy_api.utils import USERDATA_FILE, DKIM_DIR, SECRETS_FILE, get_domain
|
2024-07-24 15:15:31 +00:00
|
|
|
from selfprivacy_api.utils.block_devices import BlockDevices
|
2024-10-14 11:29:00 +00:00
|
|
|
from selfprivacy_api.utils import read_account_uri
|
2024-07-24 15:15:31 +00:00
|
|
|
|
2024-08-21 13:55:30 +00:00
|
|
|
CONFIG_STASH_DIR = "/etc/selfprivacy/dump"
|
2022-08-25 17:03:56 +00:00
|
|
|
|
2024-10-23 11:38:01 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2022-08-25 17:03:56 +00:00
|
|
|
|
2024-07-24 11:41:32 +00:00
|
|
|
class ServiceManager(Service):
|
2024-07-24 15:15:31 +00:00
|
|
|
folders: List[str] = [CONFIG_STASH_DIR]
|
|
|
|
|
2024-07-24 11:41:32 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_all_services() -> list[Service]:
|
|
|
|
return services
|
2022-08-25 17:03:56 +00:00
|
|
|
|
2024-07-24 11:41:32 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_service_by_id(service_id: str) -> typing.Optional[Service]:
|
|
|
|
for service in services:
|
|
|
|
if service.get_id() == service_id:
|
|
|
|
return service
|
|
|
|
return None
|
2022-08-25 17:03:56 +00:00
|
|
|
|
2024-07-24 11:41:32 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_enabled_services() -> list[Service]:
|
|
|
|
return [service for service in services if service.is_enabled()]
|
2022-08-25 17:03:56 +00:00
|
|
|
|
2024-07-24 15:15:31 +00:00
|
|
|
# This one is not currently used by any code.
|
2024-07-24 11:41:32 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_disabled_services() -> list[Service]:
|
|
|
|
return [service for service in services if not service.is_enabled()]
|
2022-08-25 17:03:56 +00:00
|
|
|
|
2024-07-24 11:41:32 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_services_by_location(location: str) -> list[Service]:
|
|
|
|
return [service for service in services if service.get_drive() == location]
|
2022-08-25 17:03:56 +00:00
|
|
|
|
2024-07-24 11:41:32 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_all_required_dns_records() -> list[ServiceDnsRecord]:
|
|
|
|
ip4 = network_utils.get_ip4()
|
|
|
|
ip6 = network_utils.get_ip6()
|
2024-10-14 11:29:00 +00:00
|
|
|
|
2024-10-02 12:54:41 +00:00
|
|
|
dns_records: list[ServiceDnsRecord] = []
|
|
|
|
|
2024-10-14 11:29:00 +00:00
|
|
|
try:
|
|
|
|
dns_records.append(
|
|
|
|
ServiceDnsRecord(
|
|
|
|
type="CAA",
|
|
|
|
name=get_domain(),
|
|
|
|
content=f'128 issue "letsencrypt.org;accounturi={read_account_uri()}"',
|
|
|
|
ttl=3600,
|
|
|
|
display_name="CAA record",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
except Exception as e:
|
2024-10-23 11:38:01 +00:00
|
|
|
logging.error(f"Error creating CAA: {e}")
|
2024-10-14 11:29:00 +00:00
|
|
|
|
2024-07-24 15:15:31 +00:00
|
|
|
for service in ServiceManager.get_enabled_services():
|
2024-07-24 11:41:32 +00:00
|
|
|
dns_records += service.get_dns_records(ip4, ip6)
|
|
|
|
return dns_records
|
2024-07-24 15:15:31 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_id() -> str:
|
|
|
|
"""Return service id."""
|
|
|
|
return "api"
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_display_name() -> str:
|
|
|
|
"""Return service display name."""
|
|
|
|
return "Selfprivacy API"
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_description() -> str:
|
|
|
|
"""Return service description."""
|
|
|
|
return "A proto-service for API itself. Currently manages backups of settings."
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_svg_icon() -> str:
|
|
|
|
"""Read SVG icon from file and return it as base64 encoded string."""
|
|
|
|
# return ""
|
2024-09-11 10:15:32 +00:00
|
|
|
return base64.b64encode(API_ICON.encode("utf-8")).decode("utf-8")
|
2024-07-24 15:15:31 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_url() -> typing.Optional[str]:
|
|
|
|
"""Return service url."""
|
2024-09-11 10:15:32 +00:00
|
|
|
domain = get_domain()
|
|
|
|
subdomain = ServiceManager.get_subdomain()
|
|
|
|
return f"https://{subdomain}.{domain}" if subdomain else None
|
2024-07-24 15:15:31 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_subdomain() -> typing.Optional[str]:
|
2024-09-11 10:15:32 +00:00
|
|
|
return "api"
|
2024-07-24 15:15:31 +00:00
|
|
|
|
2024-07-25 17:30:46 +00:00
|
|
|
@staticmethod
|
|
|
|
def is_always_active() -> bool:
|
|
|
|
return True
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def is_movable() -> bool:
|
2024-07-24 15:15:31 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def is_required() -> bool:
|
|
|
|
return True
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def is_enabled() -> bool:
|
|
|
|
return True
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_backup_description() -> str:
|
|
|
|
return "How did we get here?"
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_status(cls) -> ServiceStatus:
|
|
|
|
return ServiceStatus.ACTIVE
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def can_be_backed_up(cls) -> bool:
|
|
|
|
"""`True` if the service can be backed up."""
|
|
|
|
return True
|
|
|
|
|
2024-07-25 17:30:46 +00:00
|
|
|
@classmethod
|
2024-09-06 13:31:19 +00:00
|
|
|
def merge_settings(cls):
|
2024-07-24 15:15:31 +00:00
|
|
|
# For now we will just copy settings EXCEPT the locations of services
|
|
|
|
# Stash locations as they are set by user right now
|
|
|
|
locations = {}
|
|
|
|
for service in services:
|
|
|
|
locations[service.get_id()] = service.get_drive()
|
|
|
|
|
|
|
|
# Copy files
|
2024-07-25 17:30:46 +00:00
|
|
|
for p in [USERDATA_FILE, SECRETS_FILE, DKIM_DIR]:
|
|
|
|
cls.retrieve_stashed_path(p)
|
2024-07-24 15:15:31 +00:00
|
|
|
|
|
|
|
# Pop locations
|
|
|
|
for service in services:
|
|
|
|
device = BlockDevices().get_block_device(locations[service.get_id()])
|
|
|
|
if device is not None:
|
2024-07-25 17:30:46 +00:00
|
|
|
service.set_location(device)
|
2024-07-24 15:15:31 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def stop(cls):
|
2024-09-06 13:31:19 +00:00
|
|
|
"""
|
|
|
|
We are always active
|
|
|
|
"""
|
|
|
|
raise ValueError("tried to stop an always active service")
|
2024-07-24 15:15:31 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def start(cls):
|
2024-09-06 13:31:19 +00:00
|
|
|
"""
|
|
|
|
We are always active
|
|
|
|
"""
|
2024-07-24 15:15:31 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def restart(cls):
|
2024-09-06 13:31:19 +00:00
|
|
|
"""
|
|
|
|
We are always active
|
|
|
|
"""
|
2024-07-24 15:15:31 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_logs():
|
2024-09-06 13:31:19 +00:00
|
|
|
# TODO: maybe return the logs for api itself
|
2024-07-24 15:15:31 +00:00
|
|
|
return ""
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_drive(cls) -> str:
|
|
|
|
return BlockDevices().get_root_block_device().name
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_folders(cls) -> List[str]:
|
|
|
|
return cls.folders
|
|
|
|
|
2024-07-25 17:30:46 +00:00
|
|
|
@classmethod
|
|
|
|
def stash_for(cls, p: str) -> str:
|
|
|
|
basename = path.basename(p)
|
2024-09-06 13:31:19 +00:00
|
|
|
stashed_file_location = join(cls.dump_dir(), basename)
|
2024-07-25 17:30:46 +00:00
|
|
|
return stashed_file_location
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def stash_a_path(cls, p: str):
|
|
|
|
if path.isdir(p):
|
|
|
|
rmtree(cls.stash_for(p), ignore_errors=True)
|
|
|
|
copytree(p, cls.stash_for(p))
|
|
|
|
else:
|
|
|
|
copyfile(p, cls.stash_for(p))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def retrieve_stashed_path(cls, p: str):
|
|
|
|
"""
|
|
|
|
Takes an original path, hopefully it is stashed somewhere
|
|
|
|
"""
|
|
|
|
if path.isdir(p):
|
|
|
|
rmtree(p, ignore_errors=True)
|
|
|
|
copytree(cls.stash_for(p), p)
|
|
|
|
else:
|
|
|
|
copyfile(cls.stash_for(p), p)
|
|
|
|
|
2024-07-24 15:15:31 +00:00
|
|
|
@classmethod
|
|
|
|
def pre_backup(cls):
|
2024-09-06 13:31:19 +00:00
|
|
|
tempdir = cls.dump_dir()
|
2024-08-21 13:55:30 +00:00
|
|
|
if not path.exists(tempdir):
|
|
|
|
makedirs(tempdir)
|
|
|
|
|
|
|
|
paths = listdir(tempdir)
|
|
|
|
for file in paths:
|
|
|
|
remove(file)
|
2024-07-24 15:15:31 +00:00
|
|
|
|
2024-07-25 17:30:46 +00:00
|
|
|
for p in [USERDATA_FILE, SECRETS_FILE, DKIM_DIR]:
|
|
|
|
cls.stash_a_path(p)
|
2024-07-24 15:15:31 +00:00
|
|
|
|
2024-09-06 13:31:19 +00:00
|
|
|
@classmethod
|
|
|
|
def dump_dir(cls) -> str:
|
|
|
|
"""
|
|
|
|
A directory we dump our settings into
|
|
|
|
"""
|
|
|
|
return cls.folders[0]
|
|
|
|
|
2024-07-24 15:15:31 +00:00
|
|
|
@classmethod
|
|
|
|
def post_restore(cls):
|
2024-09-06 13:31:19 +00:00
|
|
|
cls.merge_settings()
|
|
|
|
rmtree(cls.dump_dir(), ignore_errors=True)
|
2024-07-24 15:15:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
services: list[Service] = [
|
|
|
|
Bitwarden(),
|
|
|
|
Forgejo(),
|
|
|
|
MailServer(),
|
|
|
|
Nextcloud(),
|
|
|
|
Pleroma(),
|
|
|
|
Ocserv(),
|
|
|
|
JitsiMeet(),
|
|
|
|
Roundcube(),
|
|
|
|
ServiceManager(),
|
|
|
|
Prometheus(),
|
|
|
|
]
|