mirror of
https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api.git
synced 2024-12-12 16:18:48 +00:00
dettlaff
848befe3f1
Reviewed-on: https://git.selfprivacy.org/SelfPrivacy/selfprivacy-rest-api/pulls/154 Reviewed-by: Inex Code <inex.code@selfprivacy.org> Co-authored-by: dettlaff <dettlaff@riseup.net> Co-committed-by: dettlaff <dettlaff@riseup.net>
334 lines
10 KiB
Python
334 lines
10 KiB
Python
"""Function to perform migration of app data to binds."""
|
|
|
|
import subprocess
|
|
import pathlib
|
|
import shutil
|
|
import logging
|
|
|
|
from pydantic import BaseModel
|
|
from selfprivacy_api.jobs import Job, JobStatus, Jobs
|
|
from selfprivacy_api.services.bitwarden import Bitwarden
|
|
from selfprivacy_api.services.forgejo import Forgejo
|
|
from selfprivacy_api.services.mailserver import MailServer
|
|
from selfprivacy_api.services.nextcloud import Nextcloud
|
|
from selfprivacy_api.services.pleroma import Pleroma
|
|
from selfprivacy_api.utils import ReadUserData, WriteUserData
|
|
from selfprivacy_api.utils.huey import huey
|
|
from selfprivacy_api.utils.block_devices import BlockDevices
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class BindMigrationConfig(BaseModel):
|
|
"""Config for bind migration.
|
|
For each service provide block device name.
|
|
"""
|
|
|
|
email_block_device: str
|
|
bitwarden_block_device: str
|
|
gitea_block_device: str
|
|
nextcloud_block_device: str
|
|
pleroma_block_device: str
|
|
|
|
|
|
def is_bind_migrated() -> bool:
|
|
"""Check if bind migration was performed."""
|
|
with ReadUserData() as user_data:
|
|
return user_data.get("useBinds", False)
|
|
|
|
|
|
def activate_binds(config: BindMigrationConfig):
|
|
"""Activate binds."""
|
|
# Activate binds in userdata
|
|
with WriteUserData() as user_data:
|
|
if "email" not in user_data:
|
|
user_data["email"] = {}
|
|
user_data["email"]["location"] = config.email_block_device
|
|
if "bitwarden" not in user_data:
|
|
user_data["bitwarden"] = {}
|
|
user_data["bitwarden"]["location"] = config.bitwarden_block_device
|
|
if "gitea" not in user_data:
|
|
user_data["gitea"] = {}
|
|
user_data["gitea"]["location"] = config.gitea_block_device
|
|
if "nextcloud" not in user_data:
|
|
user_data["nextcloud"] = {}
|
|
user_data["nextcloud"]["location"] = config.nextcloud_block_device
|
|
if "pleroma" not in user_data:
|
|
user_data["pleroma"] = {}
|
|
user_data["pleroma"]["location"] = config.pleroma_block_device
|
|
|
|
user_data["useBinds"] = True
|
|
|
|
|
|
def move_folder(
|
|
data_path: pathlib.Path, bind_path: pathlib.Path, user: str, group: str
|
|
):
|
|
"""Move folder from data to bind."""
|
|
if data_path.exists():
|
|
shutil.move(str(data_path), str(bind_path))
|
|
else:
|
|
return
|
|
|
|
try:
|
|
data_path.mkdir(mode=0o750, parents=True, exist_ok=True)
|
|
except Exception as error:
|
|
logging.error(f"Error creating data path: {error}")
|
|
return
|
|
|
|
try:
|
|
shutil.chown(str(bind_path), user=user, group=group)
|
|
shutil.chown(str(data_path), user=user, group=group)
|
|
except LookupError:
|
|
pass
|
|
|
|
try:
|
|
subprocess.run(["mount", "--bind", str(bind_path), str(data_path)], check=True)
|
|
except subprocess.CalledProcessError as error:
|
|
logging.error(error)
|
|
|
|
try:
|
|
subprocess.run(["chown", "-R", f"{user}:{group}", str(data_path)], check=True)
|
|
except subprocess.CalledProcessError as error:
|
|
logging.error(error)
|
|
|
|
|
|
@huey.task()
|
|
def migrate_to_binds(config: BindMigrationConfig, job: Job):
|
|
"""Migrate app data to binds."""
|
|
|
|
# Exit if migration is already done
|
|
if is_bind_migrated():
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.ERROR,
|
|
error="Migration already done.",
|
|
)
|
|
return
|
|
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.RUNNING,
|
|
progress=0,
|
|
status_text="Checking if all volumes are available.",
|
|
)
|
|
# Get block devices.
|
|
block_devices = BlockDevices().get_block_devices()
|
|
block_device_names = [device.name for device in block_devices]
|
|
|
|
# Get all unique required block devices
|
|
required_block_devices = []
|
|
for block_device_name in config.__dict__.values():
|
|
if block_device_name not in required_block_devices:
|
|
required_block_devices.append(block_device_name)
|
|
|
|
# Check if all block devices from config are present.
|
|
for block_device_name in required_block_devices:
|
|
if block_device_name not in block_device_names:
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.ERROR,
|
|
error=f"Block device {block_device_name} not found.",
|
|
)
|
|
return
|
|
|
|
# Make sure all required block devices are mounted.
|
|
# sda1 is the root partition and is always mounted.
|
|
for block_device_name in required_block_devices:
|
|
if block_device_name == "sda1":
|
|
continue
|
|
block_device = BlockDevices().get_block_device(block_device_name)
|
|
if block_device is None:
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.ERROR,
|
|
error=f"Block device {block_device_name} not found.",
|
|
)
|
|
return
|
|
if f"/volumes/{block_device_name}" not in block_device.mountpoints:
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.ERROR,
|
|
error=f"Block device {block_device_name} not mounted.",
|
|
)
|
|
return
|
|
|
|
# Make sure /volumes/sda1 exists.
|
|
pathlib.Path("/volumes/sda1").mkdir(parents=True, exist_ok=True)
|
|
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.RUNNING,
|
|
progress=5,
|
|
status_text="Activating binds in NixOS config.",
|
|
)
|
|
|
|
activate_binds(config)
|
|
|
|
# Perform migration of Nextcloud.
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.RUNNING,
|
|
progress=10,
|
|
status_text="Migrating Nextcloud.",
|
|
)
|
|
|
|
Nextcloud().stop()
|
|
|
|
# If /volumes/sda1/nextcloud or /volumes/sdb/nextcloud exists, skip it.
|
|
if not pathlib.Path("/volumes/sda1/nextcloud").exists():
|
|
if not pathlib.Path("/volumes/sdb/nextcloud").exists():
|
|
move_folder(
|
|
data_path=pathlib.Path("/var/lib/nextcloud"),
|
|
bind_path=pathlib.Path(
|
|
f"/volumes/{config.nextcloud_block_device}/nextcloud"
|
|
),
|
|
user="nextcloud",
|
|
group="nextcloud",
|
|
)
|
|
|
|
# Start Nextcloud
|
|
Nextcloud().start()
|
|
|
|
# Perform migration of Bitwarden
|
|
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.RUNNING,
|
|
progress=28,
|
|
status_text="Migrating Bitwarden.",
|
|
)
|
|
|
|
Bitwarden().stop()
|
|
|
|
if not pathlib.Path("/volumes/sda1/bitwarden").exists():
|
|
if not pathlib.Path("/volumes/sdb/bitwarden").exists():
|
|
move_folder(
|
|
data_path=pathlib.Path("/var/lib/bitwarden"),
|
|
bind_path=pathlib.Path(
|
|
f"/volumes/{config.bitwarden_block_device}/bitwarden"
|
|
),
|
|
user="vaultwarden",
|
|
group="vaultwarden",
|
|
)
|
|
|
|
if not pathlib.Path("/volumes/sda1/bitwarden_rs").exists():
|
|
if not pathlib.Path("/volumes/sdb/bitwarden_rs").exists():
|
|
move_folder(
|
|
data_path=pathlib.Path("/var/lib/bitwarden_rs"),
|
|
bind_path=pathlib.Path(
|
|
f"/volumes/{config.bitwarden_block_device}/bitwarden_rs"
|
|
),
|
|
user="vaultwarden",
|
|
group="vaultwarden",
|
|
)
|
|
|
|
# Start Bitwarden
|
|
Bitwarden().start()
|
|
|
|
# Perform migration of Gitea
|
|
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.RUNNING,
|
|
progress=46,
|
|
status_text="Migrating Gitea.",
|
|
)
|
|
|
|
Forgejo().stop()
|
|
|
|
if not pathlib.Path("/volumes/sda1/gitea").exists():
|
|
if not pathlib.Path("/volumes/sdb/gitea").exists():
|
|
move_folder(
|
|
data_path=pathlib.Path("/var/lib/gitea"),
|
|
bind_path=pathlib.Path(f"/volumes/{config.gitea_block_device}/gitea"),
|
|
user="gitea",
|
|
group="gitea",
|
|
)
|
|
|
|
Forgejo().start()
|
|
|
|
# Perform migration of Mail server
|
|
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.RUNNING,
|
|
progress=64,
|
|
status_text="Migrating Mail server.",
|
|
)
|
|
|
|
MailServer().stop()
|
|
|
|
if not pathlib.Path("/volumes/sda1/vmail").exists():
|
|
if not pathlib.Path("/volumes/sdb/vmail").exists():
|
|
move_folder(
|
|
data_path=pathlib.Path("/var/vmail"),
|
|
bind_path=pathlib.Path(f"/volumes/{config.email_block_device}/vmail"),
|
|
user="virtualMail",
|
|
group="virtualMail",
|
|
)
|
|
|
|
if not pathlib.Path("/volumes/sda1/sieve").exists():
|
|
if not pathlib.Path("/volumes/sdb/sieve").exists():
|
|
move_folder(
|
|
data_path=pathlib.Path("/var/sieve"),
|
|
bind_path=pathlib.Path(f"/volumes/{config.email_block_device}/sieve"),
|
|
user="virtualMail",
|
|
group="virtualMail",
|
|
)
|
|
|
|
MailServer().start()
|
|
|
|
# Perform migration of Pleroma
|
|
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.RUNNING,
|
|
progress=82,
|
|
status_text="Migrating Pleroma.",
|
|
)
|
|
|
|
Pleroma().stop()
|
|
|
|
if not pathlib.Path("/volumes/sda1/pleroma").exists():
|
|
if not pathlib.Path("/volumes/sdb/pleroma").exists():
|
|
move_folder(
|
|
data_path=pathlib.Path("/var/lib/pleroma"),
|
|
bind_path=pathlib.Path(
|
|
f"/volumes/{config.pleroma_block_device}/pleroma"
|
|
),
|
|
user="pleroma",
|
|
group="pleroma",
|
|
)
|
|
|
|
if not pathlib.Path("/volumes/sda1/postgresql").exists():
|
|
if not pathlib.Path("/volumes/sdb/postgresql").exists():
|
|
move_folder(
|
|
data_path=pathlib.Path("/var/lib/postgresql"),
|
|
bind_path=pathlib.Path(
|
|
f"/volumes/{config.pleroma_block_device}/postgresql"
|
|
),
|
|
user="postgres",
|
|
group="postgres",
|
|
)
|
|
|
|
Pleroma().start()
|
|
|
|
Jobs.update(
|
|
job=job,
|
|
status=JobStatus.FINISHED,
|
|
progress=100,
|
|
status_text="Migration finished.",
|
|
result="Migration finished.",
|
|
)
|
|
|
|
|
|
def start_bind_migration(config: BindMigrationConfig) -> Job:
|
|
"""Start migration."""
|
|
job = Jobs.add(
|
|
type_id="migrations.migrate_to_binds",
|
|
name="Migrate to binds",
|
|
description="Migration required to use the new disk space management.",
|
|
)
|
|
migrate_to_binds(config, job)
|
|
return job
|