diff --git a/selfprivacy_api/services/__init__.py b/selfprivacy_api/services/__init__.py
index 30f28a6..a688734 100644
--- a/selfprivacy_api/services/__init__.py
+++ b/selfprivacy_api/services/__init__.py
@@ -3,6 +3,7 @@
import typing
from selfprivacy_api.services.bitwarden import Bitwarden
from selfprivacy_api.services.gitea import Gitea
+from selfprivacy_api.services.jitsi import Jitsi
from selfprivacy_api.services.mailserver import MailServer
from selfprivacy_api.services.nextcloud import Nextcloud
from selfprivacy_api.services.pleroma import Pleroma
@@ -17,6 +18,7 @@ services: list[Service] = [
Nextcloud(),
Pleroma(),
Ocserv(),
+ Jitsi(),
]
@@ -59,18 +61,6 @@ def get_all_required_dns_records() -> list[ServiceDnsRecord]:
content=ip6,
ttl=3600,
),
- ServiceDnsRecord(
- type="A",
- name="meet",
- content=ip4,
- ttl=3600,
- ),
- ServiceDnsRecord(
- type="AAAA",
- name="meet",
- content=ip6,
- ttl=3600,
- ),
]
for service in get_enabled_services():
dns_records += service.get_dns_records()
diff --git a/selfprivacy_api/services/generic_status_getter.py b/selfprivacy_api/services/generic_status_getter.py
index c17f4d6..46720af 100644
--- a/selfprivacy_api/services/generic_status_getter.py
+++ b/selfprivacy_api/services/generic_status_getter.py
@@ -26,3 +26,35 @@ def get_service_status(service: str) -> ServiceStatus:
if b"ActiveState=reloading" in service_status:
return ServiceStatus.RELOADING
return ServiceStatus.OFF
+
+
+def get_service_status_from_several_units(services: list[str]) -> ServiceStatus:
+ """
+ Fetch all service statuses for all services and return the worst status.
+ Statuses from worst to best:
+ - OFF
+ - FAILED
+ - RELOADING
+ - ACTIVATING
+ - DEACTIVATING
+ - INACTIVE
+ - ACTIVE
+ """
+ service_statuses = []
+ for service in services:
+ service_statuses.append(get_service_status(service))
+ if ServiceStatus.OFF in service_statuses:
+ return ServiceStatus.OFF
+ if ServiceStatus.FAILED in service_statuses:
+ return ServiceStatus.FAILED
+ if ServiceStatus.RELOADING in service_statuses:
+ return ServiceStatus.RELOADING
+ if ServiceStatus.ACTIVATING in service_statuses:
+ return ServiceStatus.ACTIVATING
+ if ServiceStatus.DEACTIVATING in service_statuses:
+ return ServiceStatus.DEACTIVATING
+ if ServiceStatus.INACTIVE in service_statuses:
+ return ServiceStatus.INACTIVE
+ if ServiceStatus.ACTIVE in service_statuses:
+ return ServiceStatus.ACTIVE
+ return ServiceStatus.OFF
diff --git a/selfprivacy_api/services/jitsi/__init__.py b/selfprivacy_api/services/jitsi/__init__.py
new file mode 100644
index 0000000..6b3a973
--- /dev/null
+++ b/selfprivacy_api/services/jitsi/__init__.py
@@ -0,0 +1,142 @@
+"""Class representing Jitsi service"""
+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.services.generic_size_counter import get_storage_usage
+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
+
+
+class Jitsi(Service):
+ """Class representing Jitsi service"""
+
+ @staticmethod
+ def get_id() -> str:
+ """Return service id."""
+ return "jitsi"
+
+ @staticmethod
+ def get_display_name() -> str:
+ """Return service display name."""
+ return "Jitsi"
+
+ @staticmethod
+ def get_description() -> str:
+ """Return service description."""
+ return "Jitsi is a free and open-source video conferencing solution."
+
+ @staticmethod
+ def get_svg_icon() -> str:
+ """Read SVG icon from file and return it as base64 encoded string."""
+ return base64.b64encode(JITSI_ICON.encode("utf-8")).decode("utf-8")
+
+ @staticmethod
+ def get_url() -> typing.Optional[str]:
+ """Return service url."""
+ domain = get_domain()
+ return f"https://meet.{domain}"
+
+ @staticmethod
+ def is_movable() -> bool:
+ return False
+
+ @staticmethod
+ def is_required() -> bool:
+ return False
+
+ @staticmethod
+ def is_enabled() -> bool:
+ with ReadUserData() as user_data:
+ return user_data.get("jitsi", {}).get("enable", False)
+
+ @staticmethod
+ def get_status() -> ServiceStatus:
+ return get_service_status_from_several_units(
+ ["jitsi-videobridge.service", "jicofo.service"]
+ )
+
+ @staticmethod
+ def enable():
+ """Enable Jitsi service."""
+ with WriteUserData() as user_data:
+ if "jitsi" not in user_data:
+ user_data["jitsi"] = {}
+ user_data["jitsi"]["enable"] = True
+
+ @staticmethod
+ def disable():
+ """Disable Gitea service."""
+ with WriteUserData() as user_data:
+ if "jitsi" not in user_data:
+ user_data["jitsi"] = {}
+ user_data["jitsi"]["enable"] = False
+
+ @staticmethod
+ def stop():
+ subprocess.run(["systemctl", "stop", "jitsi-videobridge.service"])
+ subprocess.run(["systemctl", "stop", "jicofo.service"])
+
+ @staticmethod
+ def start():
+ subprocess.run(["systemctl", "start", "jitsi-videobridge.service"])
+ subprocess.run(["systemctl", "start", "jicofo.service"])
+
+ @staticmethod
+ def restart():
+ subprocess.run(["systemctl", "restart", "jitsi-videobridge.service"])
+ subprocess.run(["systemctl", "restart", "jicofo.service"])
+
+ @staticmethod
+ def get_configuration():
+ return {}
+
+ @staticmethod
+ def set_configuration(config_items):
+ return super().set_configuration(config_items)
+
+ @staticmethod
+ def get_logs():
+ return ""
+
+ @staticmethod
+ def get_storage_usage() -> int:
+ storage_usage = 0
+ storage_usage += get_storage_usage("/var/lib/jitsi-meet")
+ return storage_usage
+
+ @staticmethod
+ def get_location() -> str:
+ return "sda1"
+
+ @staticmethod
+ def get_dns_records() -> typing.List[ServiceDnsRecord]:
+ ip4 = network_utils.get_ip4()
+ ip6 = network_utils.get_ip6()
+ return [
+ ServiceDnsRecord(
+ type="A",
+ name="meet",
+ content=ip4,
+ ttl=3600,
+ ),
+ ServiceDnsRecord(
+ type="AAAA",
+ name="meet",
+ content=ip6,
+ ttl=3600,
+ ),
+ ]
+
+ def move_to_volume(self, volume: BlockDevice) -> Job:
+ raise NotImplementedError("jitsi service is not movable")
diff --git a/selfprivacy_api/services/jitsi/icon.py b/selfprivacy_api/services/jitsi/icon.py
new file mode 100644
index 0000000..08bcbb1
--- /dev/null
+++ b/selfprivacy_api/services/jitsi/icon.py
@@ -0,0 +1,5 @@
+JITSI_ICON = """
+
+"""
diff --git a/selfprivacy_api/services/mailserver/__init__.py b/selfprivacy_api/services/mailserver/__init__.py
index 34972a9..1a72f33 100644
--- a/selfprivacy_api/services/mailserver/__init__.py
+++ b/selfprivacy_api/services/mailserver/__init__.py
@@ -7,7 +7,10 @@ import typing
from selfprivacy_api.jobs import Job, JobStatus, Jobs
from selfprivacy_api.services.generic_service_mover import FolderMoveNames, move_service
from selfprivacy_api.services.generic_size_counter import get_storage_usage
-from selfprivacy_api.services.generic_status_getter import get_service_status
+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.utils.block_devices import BlockDevice
@@ -54,37 +57,9 @@ class MailServer(Service):
@staticmethod
def get_status() -> ServiceStatus:
- imap_status = get_service_status("dovecot2.service")
- smtp_status = get_service_status("postfix.service")
-
- if imap_status == ServiceStatus.ACTIVE and smtp_status == ServiceStatus.ACTIVE:
- return ServiceStatus.ACTIVE
- elif imap_status == ServiceStatus.FAILED or smtp_status == ServiceStatus.FAILED:
- return ServiceStatus.FAILED
- elif (
- imap_status == ServiceStatus.RELOADING
- or smtp_status == ServiceStatus.RELOADING
- ):
- return ServiceStatus.RELOADING
- elif (
- imap_status == ServiceStatus.ACTIVATING
- or smtp_status == ServiceStatus.ACTIVATING
- ):
- return ServiceStatus.ACTIVATING
- elif (
- imap_status == ServiceStatus.DEACTIVATING
- or smtp_status == ServiceStatus.DEACTIVATING
- ):
- return ServiceStatus.DEACTIVATING
- elif (
- imap_status == ServiceStatus.INACTIVE
- or smtp_status == ServiceStatus.INACTIVE
- ):
- return ServiceStatus.INACTIVE
- elif imap_status == ServiceStatus.OFF or smtp_status == ServiceStatus.OFF:
- return ServiceStatus.OFF
- else:
- return ServiceStatus.FAILED
+ return get_service_status_from_several_units(
+ ["dovecot2.service", "postfix.service"]
+ )
@staticmethod
def enable():
diff --git a/selfprivacy_api/services/mailserver/icon.py b/selfprivacy_api/services/mailserver/icon.py
index cb5b639..a688ef3 100644
--- a/selfprivacy_api/services/mailserver/icon.py
+++ b/selfprivacy_api/services/mailserver/icon.py
@@ -1,5 +1,5 @@
MAILSERVER_ICON = """
"""
diff --git a/tests/test_graphql/test_system/turned_on.json b/tests/test_graphql/test_system/turned_on.json
index 99a023c..821875b 100644
--- a/tests/test_graphql/test_system/turned_on.json
+++ b/tests/test_graphql/test_system/turned_on.json
@@ -41,6 +41,9 @@
"pleroma": {
"enable": true
},
+ "jitsi": {
+ "enable": true
+ },
"autoUpgrade": {
"enable": true,
"allowReboot": true