Merge pull request 'Volume management fixes' (#45) from fix/do-volumes into master
continuous-integration/drone/push Build is failing Details

Reviewed-on: #45
Reviewed-by: houkime <houkime@protonmail.com>
backups-unlock
Inex Code 2023-08-02 15:16:12 +03:00
commit a1267946fc
15 changed files with 92 additions and 91 deletions

View File

@ -27,4 +27,4 @@ async def get_token_header(
def get_api_version() -> str:
"""Get API version"""
return "2.2.0"
return "2.2.1"

View File

@ -23,7 +23,7 @@ class Storage:
else str(volume.size),
free_space=str(volume.fsavail),
used_space=str(volume.fsused),
root=volume.name == "sda1",
root=volume.is_root(),
name=volume.name,
model=volume.model,
serial=volume.serial,

View File

@ -3,13 +3,12 @@ import base64
import subprocess
import typing
from selfprivacy_api.jobs import Job, JobStatus, Jobs
from selfprivacy_api.jobs import Job, Jobs
from selfprivacy_api.services.generic_service_mover import FolderMoveNames, move_service
from selfprivacy_api.services.generic_status_getter import get_service_status
from selfprivacy_api.services.service import Service, ServiceDnsRecord, ServiceStatus
from selfprivacy_api.utils import ReadUserData, WriteUserData, get_domain
from selfprivacy_api.utils.block_devices import BlockDevice
from selfprivacy_api.utils.huey import huey
import selfprivacy_api.utils.network as network_utils
from selfprivacy_api.services.bitwarden.icon import BITWARDEN_ICON
@ -121,14 +120,6 @@ class Bitwarden(Service):
def get_folders() -> typing.List[str]:
return ["/var/lib/bitwarden", "/var/lib/bitwarden_rs"]
@staticmethod
def get_drive() -> str:
with ReadUserData() as user_data:
if user_data.get("useBinds", False):
return user_data.get("bitwarden", {}).get("location", "sda1")
else:
return "sda1"
@staticmethod
def get_dns_records() -> typing.List[ServiceDnsRecord]:
"""Return list of DNS records for Bitwarden service."""

View File

@ -83,7 +83,7 @@ def move_service(
)
return
# Make sure the volume is mounted
if volume.name != "sda1" and f"/volumes/{volume.name}" not in volume.mountpoints:
if not volume.is_root() and f"/volumes/{volume.name}" not in volume.mountpoints:
Jobs.update(
job=job,
status=JobStatus.ERROR,

View File

@ -9,7 +9,6 @@ from selfprivacy_api.services.generic_status_getter import get_service_status
from selfprivacy_api.services.service import Service, ServiceDnsRecord, ServiceStatus
from selfprivacy_api.utils import ReadUserData, WriteUserData, get_domain
from selfprivacy_api.utils.block_devices import BlockDevice
from selfprivacy_api.utils.huey import huey
import selfprivacy_api.utils.network as network_utils
from selfprivacy_api.services.gitea.icon import GITEA_ICON
@ -116,14 +115,6 @@ class Gitea(Service):
def get_folders() -> typing.List[str]:
return ["/var/lib/gitea"]
@staticmethod
def get_drive() -> str:
with ReadUserData() as user_data:
if user_data.get("useBinds", False):
return user_data.get("gitea", {}).get("location", "sda1")
else:
return "sda1"
@staticmethod
def get_dns_records() -> typing.List[ServiceDnsRecord]:
return [

View File

@ -3,16 +3,13 @@ import base64
import subprocess
import typing
from selfprivacy_api.jobs import Job, Jobs
from selfprivacy_api.services.generic_service_mover import FolderMoveNames, move_service
from selfprivacy_api.jobs import Job
from selfprivacy_api.services.generic_status_getter import (
get_service_status,
get_service_status_from_several_units,
)
from selfprivacy_api.services.service import Service, ServiceDnsRecord, ServiceStatus
from selfprivacy_api.utils import ReadUserData, WriteUserData, get_domain
from selfprivacy_api.utils.block_devices import BlockDevice
from selfprivacy_api.utils.huey import huey
import selfprivacy_api.utils.network as network_utils
from selfprivacy_api.services.jitsi.icon import JITSI_ICON
@ -87,18 +84,27 @@ class Jitsi(Service):
@staticmethod
def stop():
subprocess.run(["systemctl", "stop", "jitsi-videobridge.service"])
subprocess.run(["systemctl", "stop", "jicofo.service"])
subprocess.run(
["systemctl", "stop", "jitsi-videobridge.service"],
check=False,
)
subprocess.run(["systemctl", "stop", "jicofo.service"], check=False)
@staticmethod
def start():
subprocess.run(["systemctl", "start", "jitsi-videobridge.service"])
subprocess.run(["systemctl", "start", "jicofo.service"])
subprocess.run(
["systemctl", "start", "jitsi-videobridge.service"],
check=False,
)
subprocess.run(["systemctl", "start", "jicofo.service"], check=False)
@staticmethod
def restart():
subprocess.run(["systemctl", "restart", "jitsi-videobridge.service"])
subprocess.run(["systemctl", "restart", "jicofo.service"])
subprocess.run(
["systemctl", "restart", "jitsi-videobridge.service"],
check=False,
)
subprocess.run(["systemctl", "restart", "jicofo.service"], check=False)
@staticmethod
def get_configuration():
@ -116,10 +122,6 @@ class Jitsi(Service):
def get_folders() -> typing.List[str]:
return ["/var/lib/jitsi-meet"]
@staticmethod
def get_drive() -> str:
return "sda1"
@staticmethod
def get_dns_records() -> typing.List[ServiceDnsRecord]:
ip4 = network_utils.get_ip4()

View File

@ -4,16 +4,14 @@ import base64
import subprocess
import typing
from selfprivacy_api.jobs import Job, JobStatus, Jobs
from selfprivacy_api.jobs import Job, Jobs
from selfprivacy_api.services.generic_service_mover import FolderMoveNames, move_service
from selfprivacy_api.services.generic_status_getter import (
get_service_status,
get_service_status_from_several_units,
)
from selfprivacy_api.services.service import Service, ServiceDnsRecord, ServiceStatus
import selfprivacy_api.utils as utils
from selfprivacy_api import utils
from selfprivacy_api.utils.block_devices import BlockDevice
from selfprivacy_api.utils.huey import huey
import selfprivacy_api.utils.network as network_utils
from selfprivacy_api.services.mailserver.icon import MAILSERVER_ICON
@ -23,7 +21,7 @@ class MailServer(Service):
@staticmethod
def get_id() -> str:
return "mailserver"
return "email"
@staticmethod
def get_display_name() -> str:
@ -78,18 +76,18 @@ class MailServer(Service):
@staticmethod
def stop():
subprocess.run(["systemctl", "stop", "dovecot2.service"])
subprocess.run(["systemctl", "stop", "postfix.service"])
subprocess.run(["systemctl", "stop", "dovecot2.service"], check=False)
subprocess.run(["systemctl", "stop", "postfix.service"], check=False)
@staticmethod
def start():
subprocess.run(["systemctl", "start", "dovecot2.service"])
subprocess.run(["systemctl", "start", "postfix.service"])
subprocess.run(["systemctl", "start", "dovecot2.service"], check=False)
subprocess.run(["systemctl", "start", "postfix.service"], check=False)
@staticmethod
def restart():
subprocess.run(["systemctl", "restart", "dovecot2.service"])
subprocess.run(["systemctl", "restart", "postfix.service"])
subprocess.run(["systemctl", "restart", "dovecot2.service"], check=False)
subprocess.run(["systemctl", "restart", "postfix.service"], check=False)
@staticmethod
def get_configuration():
@ -107,14 +105,6 @@ class MailServer(Service):
def get_folders() -> typing.List[str]:
return ["/var/vmail", "/var/sieve"]
@staticmethod
def get_drive() -> str:
with utils.ReadUserData() as user_data:
if user_data.get("useBinds", False):
return user_data.get("mailserver", {}).get("location", "sda1")
else:
return "sda1"
@staticmethod
def get_dns_records() -> typing.List[ServiceDnsRecord]:
domain = utils.get_domain()
@ -142,7 +132,7 @@ class MailServer(Service):
type="MX", name=domain, content=domain, ttl=3600, priority=10
),
ServiceDnsRecord(
type="TXT", name="_dmarc", content=f"v=DMARC1; p=none", ttl=18000
type="TXT", name="_dmarc", content="v=DMARC1; p=none", ttl=18000
),
ServiceDnsRecord(
type="TXT",
@ -157,7 +147,7 @@ class MailServer(Service):
def move_to_volume(self, volume: BlockDevice) -> Job:
job = Jobs.add(
type_id="services.mailserver.move",
type_id="services.email.move",
name="Move Mail Server",
description=f"Moving mailserver data to {volume.name}",
)
@ -167,7 +157,7 @@ class MailServer(Service):
volume,
job,
FolderMoveNames.default_foldermoves(self),
"mailserver",
"email",
)
return job

View File

@ -120,15 +120,6 @@ class Nextcloud(Service):
def get_folders() -> typing.List[str]:
return ["/var/lib/nextcloud"]
@staticmethod
def get_drive() -> str:
"""Get the name of disk where Nextcloud is installed."""
with ReadUserData() as user_data:
if user_data.get("useBinds", False):
return user_data.get("nextcloud", {}).get("location", "sda1")
else:
return "sda1"
@staticmethod
def get_dns_records() -> typing.List[ServiceDnsRecord]:
return [

View File

@ -2,8 +2,7 @@
import base64
import subprocess
import typing
from selfprivacy_api.jobs import Job, Jobs
from selfprivacy_api.services.generic_service_mover import FolderMoveNames, move_service
from selfprivacy_api.jobs import Job
from selfprivacy_api.services.generic_status_getter import get_service_status
from selfprivacy_api.services.service import Service, ServiceDnsRecord, ServiceStatus
from selfprivacy_api.utils import ReadUserData, WriteUserData
@ -77,15 +76,15 @@ class Ocserv(Service):
@staticmethod
def stop():
subprocess.run(["systemctl", "stop", "ocserv.service"])
subprocess.run(["systemctl", "stop", "ocserv.service"], check=False)
@staticmethod
def start():
subprocess.run(["systemctl", "start", "ocserv.service"])
subprocess.run(["systemctl", "start", "ocserv.service"], check=False)
@staticmethod
def restart():
subprocess.run(["systemctl", "restart", "ocserv.service"])
subprocess.run(["systemctl", "restart", "ocserv.service"], check=False)
@staticmethod
def get_configuration():
@ -99,10 +98,6 @@ class Ocserv(Service):
def get_logs():
return ""
@staticmethod
def get_drive() -> str:
return "sda1"
@staticmethod
def get_dns_records() -> typing.List[ServiceDnsRecord]:
return [

View File

@ -119,14 +119,6 @@ class Pleroma(Service):
),
]
@staticmethod
def get_drive() -> str:
with ReadUserData() as user_data:
if user_data.get("useBinds", False):
return user_data.get("pleroma", {}).get("location", "sda1")
else:
return "sda1"
@staticmethod
def get_dns_records() -> typing.List[ServiceDnsRecord]:
return [

View File

@ -6,10 +6,11 @@ import typing
from pydantic import BaseModel
from selfprivacy_api.jobs import Job
from selfprivacy_api.utils.block_devices import BlockDevice
from selfprivacy_api.utils.block_devices import BlockDevice, BlockDevices
from selfprivacy_api.services.generic_size_counter import get_storage_usage
from selfprivacy_api.services.owned_path import OwnedPath
from selfprivacy_api import utils
from selfprivacy_api.utils.waitloop import wait_until_true
DEFAULT_START_STOP_TIMEOUT = 10 * 60
@ -197,10 +198,23 @@ class Service(ABC):
def get_dns_records() -> typing.List[ServiceDnsRecord]:
pass
@staticmethod
@abstractmethod
def get_drive() -> str:
pass
@classmethod
def get_drive(cls) -> str:
"""
Get the name of the drive/volume where the service is located.
Example values are `sda1`, `vda`, `sdb`.
"""
root_device: str = BlockDevices().get_root_block_device().name
if not cls.is_movable():
return root_device
with utils.ReadUserData() as userdata:
if userdata.get("useBinds", False):
return userdata.get(cls.get_id(), {}).get(
"location",
root_device,
)
else:
return root_device
@classmethod
def get_folders(cls) -> typing.List[str]:

View File

@ -10,7 +10,6 @@ from os import path
from selfprivacy_api.jobs import Job
from selfprivacy_api.services.service import Service, ServiceDnsRecord, ServiceStatus
from selfprivacy_api.utils import ReadUserData, get_domain
from selfprivacy_api.utils.block_devices import BlockDevice
import selfprivacy_api.utils.network as network_utils

View File

@ -71,6 +71,12 @@ class BlockDevice:
def __hash__(self):
return hash(self.name)
def is_root(self) -> bool:
"""
Return True if the block device is the root device.
"""
return "/" in self.mountpoints
def stats(self) -> typing.Dict[str, typing.Any]:
"""
Update current data and return a dictionary of stats.
@ -175,6 +181,9 @@ class BlockDevices(metaclass=SingletonMetaclass):
# Ignore devices with type "rom"
if device["type"] == "rom":
continue
# Ignore iso9660 devices
if device["fstype"] == "iso9660":
continue
if device["fstype"] is None:
if "children" in device:
for child in device["children"]:
@ -218,3 +227,12 @@ class BlockDevices(metaclass=SingletonMetaclass):
if mountpoint in block_device.mountpoints:
block_devices.append(block_device)
return block_devices
def get_root_block_device(self) -> BlockDevice:
"""
Return the root block device.
"""
for block_device in self.block_devices:
if "/" in block_device.mountpoints:
return block_device
raise RuntimeError("No root block device found")

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="selfprivacy_api",
version="2.2.0",
version="2.2.1",
packages=find_packages(),
scripts=[
"selfprivacy_api/app.py",

View File

@ -488,3 +488,21 @@ def test_get_block_devices_by_mountpoint(lsblk_full_mock, authorized_client):
def test_get_block_devices_by_mountpoint_no_match(lsblk_full_mock, authorized_client):
block_devices = BlockDevices().get_block_devices_by_mountpoint("/foo")
assert len(block_devices) == 0
def test_get_root_block_device(lsblk_full_mock, authorized_client):
block_device = BlockDevices().get_root_block_device()
assert block_device is not None
assert block_device.name == "sda1"
assert block_device.path == "/dev/sda1"
assert block_device.fsavail == "4605702144"
assert block_device.fssize == "19814920192"
assert block_device.fstype == "ext4"
assert block_device.fsused == "14353719296"
assert block_device.mountpoints == ["/nix/store", "/"]
assert block_device.label is None
assert block_device.uuid == "ec80c004-baec-4a2c-851d-0e1807135511"
assert block_device.size == "20210236928"
assert block_device.model is None
assert block_device.serial is None
assert block_device.type == "part"