optimized installer
This commit is contained in:
+607
@@ -0,0 +1,607 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .database import get_db
|
||||||
|
|
||||||
|
|
||||||
|
SOCKET_PATH = Path("/run/parental-control.sock")
|
||||||
|
|
||||||
|
BUFFER_SIZE = 65536
|
||||||
|
|
||||||
|
POLKIT_ACTION = "org.sarlink.parentalcontrol.modify"
|
||||||
|
|
||||||
|
|
||||||
|
def get_peer_credentials(connection):
|
||||||
|
credentials = connection.getsockopt(
|
||||||
|
socket.SOL_SOCKET,
|
||||||
|
socket.SO_PEERCRED,
|
||||||
|
struct.calcsize("3i"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return struct.unpack("3i", credentials)
|
||||||
|
|
||||||
|
|
||||||
|
def authorize_write(pid: int) -> bool:
|
||||||
|
try:
|
||||||
|
os.kill(pid, 0)
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
"pkcheck",
|
||||||
|
"--action-id",
|
||||||
|
POLKIT_ACTION,
|
||||||
|
"--process",
|
||||||
|
str(pid),
|
||||||
|
"--allow-user-interaction",
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class IPCOperations:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_users():
|
||||||
|
with get_db() as db:
|
||||||
|
rows = db.execute(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM users
|
||||||
|
ORDER BY username COLLATE NOCASE
|
||||||
|
"""
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_user(user_id):
|
||||||
|
with get_db() as db:
|
||||||
|
row = db.execute(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM users
|
||||||
|
WHERE id = ?
|
||||||
|
""",
|
||||||
|
(user_id,),
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
return dict(row) if row else None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_remaining_time(user_id):
|
||||||
|
with get_db() as db:
|
||||||
|
allowance_row = db.execute(
|
||||||
|
"""
|
||||||
|
SELECT COALESCE(
|
||||||
|
allowance_seconds,
|
||||||
|
0
|
||||||
|
) AS total
|
||||||
|
FROM daily_allowances
|
||||||
|
WHERE user_id = ?
|
||||||
|
AND weekday = CAST(
|
||||||
|
strftime('%w', 'now', 'localtime')
|
||||||
|
AS INTEGER
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
(user_id,),
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
allowance = (
|
||||||
|
allowance_row["total"]
|
||||||
|
if allowance_row
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
usage_row = db.execute(
|
||||||
|
"""
|
||||||
|
SELECT COALESCE(
|
||||||
|
used_seconds,
|
||||||
|
0
|
||||||
|
) AS total
|
||||||
|
FROM usage
|
||||||
|
WHERE user_id = ?
|
||||||
|
AND date = date('now', 'localtime')
|
||||||
|
""",
|
||||||
|
(user_id,),
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
used = (
|
||||||
|
usage_row["total"]
|
||||||
|
if usage_row
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
remaining = max(
|
||||||
|
allowance - used,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
return remaining, allowance
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_allowances(user_id):
|
||||||
|
with get_db() as db:
|
||||||
|
rows = db.execute(
|
||||||
|
"""
|
||||||
|
SELECT weekday, allowance_seconds
|
||||||
|
FROM daily_allowances
|
||||||
|
WHERE user_id = ?
|
||||||
|
""",
|
||||||
|
(user_id,),
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
return {
|
||||||
|
row["weekday"]: row["allowance_seconds"]
|
||||||
|
for row in rows
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_access_windows(user_id):
|
||||||
|
with get_db() as db:
|
||||||
|
rows = db.execute(
|
||||||
|
"""
|
||||||
|
SELECT weekday, start_minute, end_minute
|
||||||
|
FROM access_windows
|
||||||
|
WHERE user_id = ?
|
||||||
|
ORDER BY weekday, start_minute
|
||||||
|
""",
|
||||||
|
(user_id,),
|
||||||
|
).fetchall()
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
result.setdefault(
|
||||||
|
row["weekday"],
|
||||||
|
[],
|
||||||
|
).append(
|
||||||
|
(
|
||||||
|
row["start_minute"],
|
||||||
|
row["end_minute"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_user(
|
||||||
|
username,
|
||||||
|
enabled,
|
||||||
|
allowances,
|
||||||
|
access_windows,
|
||||||
|
):
|
||||||
|
with get_db() as db:
|
||||||
|
cursor = db.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO users (
|
||||||
|
username,
|
||||||
|
enabled
|
||||||
|
)
|
||||||
|
VALUES (?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
username,
|
||||||
|
1 if enabled else 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
user_id = cursor.lastrowid
|
||||||
|
|
||||||
|
for weekday, seconds in allowances.items():
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO daily_allowances (
|
||||||
|
user_id,
|
||||||
|
weekday,
|
||||||
|
allowance_seconds
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
user_id,
|
||||||
|
weekday,
|
||||||
|
seconds,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
for weekday, windows in access_windows.items():
|
||||||
|
for start_minute, end_minute in windows:
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO access_windows (
|
||||||
|
user_id,
|
||||||
|
weekday,
|
||||||
|
start_minute,
|
||||||
|
end_minute
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
user_id,
|
||||||
|
weekday,
|
||||||
|
start_minute,
|
||||||
|
end_minute,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return user_id
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_user(
|
||||||
|
user_id,
|
||||||
|
enabled,
|
||||||
|
allowances,
|
||||||
|
access_windows,
|
||||||
|
):
|
||||||
|
with get_db() as db:
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
UPDATE users
|
||||||
|
SET enabled = ?
|
||||||
|
WHERE id = ?
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
1 if enabled else 0,
|
||||||
|
user_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
for weekday, seconds in allowances.items():
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO daily_allowances (
|
||||||
|
user_id,
|
||||||
|
weekday,
|
||||||
|
allowance_seconds
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT(user_id, weekday)
|
||||||
|
DO UPDATE SET
|
||||||
|
allowance_seconds =
|
||||||
|
excluded.allowance_seconds
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
user_id,
|
||||||
|
weekday,
|
||||||
|
seconds,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM access_windows
|
||||||
|
WHERE user_id = ?
|
||||||
|
""",
|
||||||
|
(user_id,),
|
||||||
|
)
|
||||||
|
|
||||||
|
for weekday, windows in access_windows.items():
|
||||||
|
for start_minute, end_minute in windows:
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO access_windows (
|
||||||
|
user_id,
|
||||||
|
weekday,
|
||||||
|
start_minute,
|
||||||
|
end_minute
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
user_id,
|
||||||
|
weekday,
|
||||||
|
start_minute,
|
||||||
|
end_minute,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_user(user_id):
|
||||||
|
with get_db() as db:
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM users
|
||||||
|
WHERE id = ?
|
||||||
|
""",
|
||||||
|
(user_id,),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_enabled(user_id, enabled):
|
||||||
|
with get_db() as db:
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
UPDATE users
|
||||||
|
SET enabled = ?
|
||||||
|
WHERE id = ?
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
1 if enabled else 0,
|
||||||
|
user_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_temporary_time(
|
||||||
|
user_id,
|
||||||
|
seconds,
|
||||||
|
):
|
||||||
|
with get_db() as db:
|
||||||
|
db.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO temporary_grants (
|
||||||
|
user_id,
|
||||||
|
seconds,
|
||||||
|
remaining_seconds
|
||||||
|
)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
user_id,
|
||||||
|
seconds,
|
||||||
|
seconds,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
READ_METHODS = {
|
||||||
|
"get_users",
|
||||||
|
"get_user",
|
||||||
|
"get_remaining_time",
|
||||||
|
"get_allowances",
|
||||||
|
"get_access_windows",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WRITE_METHODS = {
|
||||||
|
"create_user",
|
||||||
|
"update_user",
|
||||||
|
"delete_user",
|
||||||
|
"set_enabled",
|
||||||
|
"add_temporary_time",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def handle_request(request, pid):
|
||||||
|
method = request.get("method")
|
||||||
|
arguments = request.get("args", {})
|
||||||
|
|
||||||
|
if method in READ_METHODS:
|
||||||
|
operation = getattr(
|
||||||
|
IPCOperations,
|
||||||
|
method,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if operation is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Unknown method: {method}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return operation(**arguments)
|
||||||
|
|
||||||
|
if method in WRITE_METHODS:
|
||||||
|
if not authorize_write(pid):
|
||||||
|
raise PermissionError(
|
||||||
|
"Administrative authorization required"
|
||||||
|
)
|
||||||
|
|
||||||
|
operation = getattr(
|
||||||
|
IPCOperations,
|
||||||
|
method,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if operation is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Unknown method: {method}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return operation(**arguments)
|
||||||
|
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Unknown IPC method: {method}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_client(connection):
|
||||||
|
try:
|
||||||
|
pid, uid, gid = get_peer_credentials(
|
||||||
|
connection
|
||||||
|
)
|
||||||
|
|
||||||
|
data = bytearray()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
chunk = connection.recv(
|
||||||
|
BUFFER_SIZE
|
||||||
|
)
|
||||||
|
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
|
||||||
|
data.extend(chunk)
|
||||||
|
|
||||||
|
if b"\n" in chunk:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
|
line = bytes(data).split(
|
||||||
|
b"\n",
|
||||||
|
1,
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
request = json.loads(
|
||||||
|
line.decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = handle_request(
|
||||||
|
request,
|
||||||
|
pid,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"ok": True,
|
||||||
|
"result": result,
|
||||||
|
}
|
||||||
|
|
||||||
|
except PermissionError as exc:
|
||||||
|
response = {
|
||||||
|
"ok": False,
|
||||||
|
"error": str(exc),
|
||||||
|
"error_type": "authorization",
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
response = {
|
||||||
|
"ok": False,
|
||||||
|
"error": str(exc),
|
||||||
|
"error_type": "server",
|
||||||
|
}
|
||||||
|
|
||||||
|
payload = (
|
||||||
|
json.dumps(
|
||||||
|
response,
|
||||||
|
separators=(",", ":"),
|
||||||
|
).encode("utf-8")
|
||||||
|
+ b"\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
connection.sendall(payload)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
connection.sendall(
|
||||||
|
b'{"ok":false,"error":"Invalid request",'
|
||||||
|
b'"error_type":"server"}\n'
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
finally:
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
|
||||||
|
class IPCServer:
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
socket_path=SOCKET_PATH,
|
||||||
|
):
|
||||||
|
self.socket_path = Path(
|
||||||
|
socket_path
|
||||||
|
)
|
||||||
|
|
||||||
|
self.server_socket = None
|
||||||
|
self._thread = None
|
||||||
|
self._stop_event = threading.Event()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
if self._thread is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.socket_path.parent.mkdir(
|
||||||
|
parents=True,
|
||||||
|
exist_ok=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.socket_path.unlink()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.server_socket = socket.socket(
|
||||||
|
socket.AF_UNIX,
|
||||||
|
socket.SOCK_STREAM,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.server_socket.bind(
|
||||||
|
str(self.socket_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
os.chmod(
|
||||||
|
self.socket_path,
|
||||||
|
0o666,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.server_socket.listen(16)
|
||||||
|
|
||||||
|
self._stop_event.clear()
|
||||||
|
|
||||||
|
self._thread = threading.Thread(
|
||||||
|
target=self._run,
|
||||||
|
name="parental-control-ipc",
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._thread.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self._stop_event.set()
|
||||||
|
|
||||||
|
if self.server_socket is not None:
|
||||||
|
try:
|
||||||
|
self.server_socket.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self._thread is not None:
|
||||||
|
self._thread.join(timeout=2)
|
||||||
|
|
||||||
|
self._thread = None
|
||||||
|
self.server_socket = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.socket_path.unlink()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _run(self):
|
||||||
|
while not self._stop_event.is_set():
|
||||||
|
try:
|
||||||
|
connection, _ = (
|
||||||
|
self.server_socket.accept()
|
||||||
|
)
|
||||||
|
|
||||||
|
except OSError:
|
||||||
|
if self._stop_event.is_set():
|
||||||
|
break
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=handle_client,
|
||||||
|
args=(connection,),
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
ipc_server = IPCServer()
|
||||||
|
|
||||||
|
|
||||||
|
def start_ipc_server():
|
||||||
|
ipc_server.start()
|
||||||
|
|
||||||
|
|
||||||
|
def stop_ipc_server():
|
||||||
|
ipc_server.stop()
|
||||||
@@ -2,19 +2,24 @@ import time
|
|||||||
|
|
||||||
from .database import initialize_database
|
from .database import initialize_database
|
||||||
from .scheduler import start_scheduler, stop_scheduler
|
from .scheduler import start_scheduler, stop_scheduler
|
||||||
|
from .ipc import start_ipc_server, stop_ipc_server
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
initialize_database()
|
initialize_database()
|
||||||
|
start_ipc_server()
|
||||||
start_scheduler()
|
start_scheduler()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
time.sleep(60)
|
time.sleep(60)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
stop_scheduler()
|
stop_scheduler()
|
||||||
|
stop_ipc_server()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+710
-78
@@ -1,110 +1,525 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
set -e
|
set -euo pipefail
|
||||||
|
|
||||||
# ==========================================
|
# ============================================================
|
||||||
# Linux Parental Control Installer
|
# Linux Parental Control Installer
|
||||||
# ==========================================
|
# ============================================================
|
||||||
|
|
||||||
|
APP_NAME="Parental Control"
|
||||||
|
SERVICE_NAME="parental-control.service"
|
||||||
|
DESKTOP_ID="parental-control.desktop"
|
||||||
|
|
||||||
|
INSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
VENV_DIR="$INSTALL_DIR/.venv"
|
||||||
|
PYTHON="$VENV_DIR/bin/python"
|
||||||
|
|
||||||
|
SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME"
|
||||||
|
DESKTOP_FILE="/usr/share/applications/$DESKTOP_ID"
|
||||||
|
|
||||||
|
POLKIT_ACTION="org.sarlink.parentalcontrol.modify"
|
||||||
|
POLKIT_SOURCE="$INSTALL_DIR/$POLKIT_ACTION.policy"
|
||||||
|
POLKIT_DEST="/etc/polkit-1/actions/$POLKIT_ACTION.policy"
|
||||||
|
|
||||||
|
SOCKET_PATH="/run/parental-control.sock"
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Output helpers
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info() {
|
||||||
|
echo
|
||||||
|
echo "==> $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
success() {
|
||||||
|
echo " $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
warning() {
|
||||||
|
echo " WARNING: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
error() {
|
||||||
|
echo
|
||||||
|
echo "ERROR: $1"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Root check
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
echo "Please run with sudo:"
|
echo
|
||||||
|
echo "This installer requires administrator privileges."
|
||||||
|
echo
|
||||||
|
echo "Run:"
|
||||||
|
echo
|
||||||
echo " sudo ./install.sh"
|
echo " sudo ./install.sh"
|
||||||
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INSTALL_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
|
|
||||||
APP_NAME="Parental Control"
|
# ============================================================
|
||||||
DESKTOP_FILE="/usr/share/applications/parental-control.desktop"
|
# Find original user
|
||||||
SERVICE_FILE="/etc/systemd/system/parental-control.service"
|
#
|
||||||
|
# When running through sudo, SUDO_USER is the person who
|
||||||
|
# launched the installer. We use this to keep the source tree
|
||||||
|
# owned by that user rather than root.
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
|
||||||
|
INSTALL_USER="$SUDO_USER"
|
||||||
|
else
|
||||||
|
INSTALL_USER="$(stat -c '%U' "$INSTALL_DIR")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! id "$INSTALL_USER" >/dev/null 2>&1; then
|
||||||
|
error "Could not determine the normal installation user."
|
||||||
|
fi
|
||||||
|
|
||||||
|
INSTALL_GROUP="$(id -gn "$INSTALL_USER")"
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Header
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
clear 2>/dev/null || true
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " Linux Parental Control Installer"
|
echo " Linux Parental Control Installer"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo
|
echo
|
||||||
echo "Installing from:"
|
echo "Application:"
|
||||||
|
echo " $APP_NAME"
|
||||||
|
echo
|
||||||
|
echo "Installation directory:"
|
||||||
echo " $INSTALL_DIR"
|
echo " $INSTALL_DIR"
|
||||||
echo
|
echo
|
||||||
|
echo "Installation user:"
|
||||||
# ------------------------------------------
|
echo " $INSTALL_USER"
|
||||||
# 1. System dependencies
|
|
||||||
# ------------------------------------------
|
|
||||||
|
|
||||||
echo "[1/6] Installing system dependencies"
|
|
||||||
|
|
||||||
pacman -S --needed --noconfirm \
|
|
||||||
python \
|
|
||||||
python-pip
|
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "System dependencies installed."
|
|
||||||
|
|
||||||
# ------------------------------------------
|
|
||||||
# 2. Python virtual environment
|
|
||||||
# ------------------------------------------
|
|
||||||
|
|
||||||
echo
|
# ============================================================
|
||||||
echo "[2/6] Creating Python virtual environment"
|
# Check project files
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
if [ ! -d "$INSTALL_DIR/.venv" ]; then
|
info "Checking project files"
|
||||||
python -m venv "$INSTALL_DIR/.venv"
|
|
||||||
else
|
REQUIRED_FILES=(
|
||||||
echo "Virtual environment already exists."
|
"requirements.txt"
|
||||||
|
"parental-control.service"
|
||||||
|
"app/__init__.py"
|
||||||
|
"app/main.py"
|
||||||
|
"app/database.py"
|
||||||
|
"app/enforcement.py"
|
||||||
|
"app/scheduler.py"
|
||||||
|
"app/users.py"
|
||||||
|
"app/ipc.py"
|
||||||
|
"desktop/__init__.py"
|
||||||
|
"desktop/main.py"
|
||||||
|
"desktop/client.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
for file in "${REQUIRED_FILES[@]}"; do
|
||||||
|
if [ ! -f "$INSTALL_DIR/$file" ]; then
|
||||||
|
error "Required file not found: $file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
success "All required project files found."
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Check Linux
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Detecting operating system"
|
||||||
|
|
||||||
|
if [ ! -f /etc/os-release ]; then
|
||||||
|
error "/etc/os-release was not found."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
# shellcheck disable=SC1091
|
||||||
echo "Virtual environment ready."
|
source /etc/os-release
|
||||||
|
|
||||||
# ------------------------------------------
|
OS_ID="${ID:-unknown}"
|
||||||
# 3. Python dependencies
|
OS_NAME="${NAME:-Unknown Linux}"
|
||||||
# ------------------------------------------
|
OS_VERSION="${VERSION_ID:-unknown}"
|
||||||
|
|
||||||
echo
|
echo " Distribution: $OS_NAME"
|
||||||
echo "[3/6] Installing Python packages"
|
echo " Version: $OS_VERSION"
|
||||||
|
|
||||||
"$INSTALL_DIR/.venv/bin/python" -m pip install \
|
|
||||||
|
# ============================================================
|
||||||
|
# Check systemd
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Checking systemd"
|
||||||
|
|
||||||
|
if ! command -v systemctl >/dev/null 2>&1; then
|
||||||
|
error "systemd is required, but systemctl was not found."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d /run/systemd/system ]; then
|
||||||
|
error "systemd is installed but does not appear to be running."
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "systemd detected."
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Detect package manager
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Detecting package manager"
|
||||||
|
|
||||||
|
PACKAGE_MANAGER=""
|
||||||
|
|
||||||
|
if command -v pacman >/dev/null 2>&1; then
|
||||||
|
PACKAGE_MANAGER="pacman"
|
||||||
|
|
||||||
|
elif command -v apt-get >/dev/null 2>&1; then
|
||||||
|
PACKAGE_MANAGER="apt"
|
||||||
|
|
||||||
|
elif command -v dnf >/dev/null 2>&1; then
|
||||||
|
PACKAGE_MANAGER="dnf"
|
||||||
|
|
||||||
|
elif command -v zypper >/dev/null 2>&1; then
|
||||||
|
PACKAGE_MANAGER="zypper"
|
||||||
|
|
||||||
|
elif command -v apk >/dev/null 2>&1; then
|
||||||
|
PACKAGE_MANAGER="apk"
|
||||||
|
|
||||||
|
elif command -v xbps-install >/dev/null 2>&1; then
|
||||||
|
PACKAGE_MANAGER="xbps"
|
||||||
|
|
||||||
|
else
|
||||||
|
error "No supported package manager was detected.
|
||||||
|
|
||||||
|
Supported package managers:
|
||||||
|
pacman
|
||||||
|
apt
|
||||||
|
dnf
|
||||||
|
zypper
|
||||||
|
apk
|
||||||
|
xbps-install"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Package manager: $PACKAGE_MANAGER"
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Install system packages
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Installing system dependencies"
|
||||||
|
|
||||||
|
case "$PACKAGE_MANAGER" in
|
||||||
|
|
||||||
|
pacman)
|
||||||
|
pacman -S --needed --noconfirm \
|
||||||
|
python \
|
||||||
|
python-pip \
|
||||||
|
polkit \
|
||||||
|
desktop-file-utils
|
||||||
|
;;
|
||||||
|
|
||||||
|
apt)
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
|
||||||
|
apt-get install -y \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
python3-venv \
|
||||||
|
polkitd \
|
||||||
|
policykit-1 \
|
||||||
|
desktop-file-utils
|
||||||
|
;;
|
||||||
|
|
||||||
|
dnf)
|
||||||
|
dnf install -y \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
polkit \
|
||||||
|
desktop-file-utils
|
||||||
|
;;
|
||||||
|
|
||||||
|
zypper)
|
||||||
|
zypper --non-interactive install \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
polkit \
|
||||||
|
desktop-file-utils
|
||||||
|
;;
|
||||||
|
|
||||||
|
apk)
|
||||||
|
apk add \
|
||||||
|
python3 \
|
||||||
|
py3-pip \
|
||||||
|
py3-virtualenv \
|
||||||
|
polkit \
|
||||||
|
desktop-file-utils
|
||||||
|
;;
|
||||||
|
|
||||||
|
xbps)
|
||||||
|
xbps-install -Sy \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
polkit \
|
||||||
|
desktop-file-utils
|
||||||
|
;;
|
||||||
|
|
||||||
|
esac
|
||||||
|
|
||||||
|
success "System dependencies installed."
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Find Python
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Checking Python"
|
||||||
|
|
||||||
|
SYSTEM_PYTHON=""
|
||||||
|
|
||||||
|
for candidate in python3 python; do
|
||||||
|
if command -v "$candidate" >/dev/null 2>&1; then
|
||||||
|
SYSTEM_PYTHON="$(command -v "$candidate")"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$SYSTEM_PYTHON" ]; then
|
||||||
|
error "Python 3 could not be found."
|
||||||
|
fi
|
||||||
|
|
||||||
|
PYTHON_MAJOR="$(
|
||||||
|
"$SYSTEM_PYTHON" -c 'import sys; print(sys.version_info.major)'
|
||||||
|
)"
|
||||||
|
|
||||||
|
PYTHON_MINOR="$(
|
||||||
|
"$SYSTEM_PYTHON" -c 'import sys; print(sys.version_info.minor)'
|
||||||
|
)"
|
||||||
|
|
||||||
|
PYTHON_VERSION="$(
|
||||||
|
"$SYSTEM_PYTHON" -c \
|
||||||
|
'import sys; print(".".join(map(str, sys.version_info[:3])))'
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [ "$PYTHON_MAJOR" -ne 3 ]; then
|
||||||
|
error "Python 3 is required. Found Python $PYTHON_VERSION."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " Python: $SYSTEM_PYTHON"
|
||||||
|
echo " Version: $PYTHON_VERSION"
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Check Python version for PySide6
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
if [ "$PYTHON_MINOR" -lt 9 ]; then
|
||||||
|
error "Python $PYTHON_VERSION is too old for this application.
|
||||||
|
|
||||||
|
Python 3.9 or newer is required."
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Python version is supported."
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Create/recreate virtual environment
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Preparing Python virtual environment"
|
||||||
|
|
||||||
|
if [ -x "$PYTHON" ]; then
|
||||||
|
success "Existing virtual environment is valid."
|
||||||
|
|
||||||
|
else
|
||||||
|
if [ -d "$VENV_DIR" ]; then
|
||||||
|
echo " Existing virtual environment is broken."
|
||||||
|
echo " Removing it..."
|
||||||
|
|
||||||
|
rm -rf "$VENV_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " Creating virtual environment..."
|
||||||
|
|
||||||
|
if ! "$SYSTEM_PYTHON" -m venv "$VENV_DIR"; then
|
||||||
|
error "Could not create the Python virtual environment.
|
||||||
|
|
||||||
|
Your distribution may require an additional Python venv package."
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Virtual environment created."
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Install Python dependencies
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Installing Python dependencies"
|
||||||
|
|
||||||
|
"$PYTHON" -m pip install \
|
||||||
|
--disable-pip-version-check \
|
||||||
--upgrade pip
|
--upgrade pip
|
||||||
|
|
||||||
"$INSTALL_DIR/.venv/bin/python" -m pip install \
|
"$PYTHON" -m pip install \
|
||||||
|
--disable-pip-version-check \
|
||||||
-r "$INSTALL_DIR/requirements.txt"
|
-r "$INSTALL_DIR/requirements.txt"
|
||||||
|
|
||||||
echo
|
success "Python dependencies installed."
|
||||||
echo "Python packages installed."
|
|
||||||
|
|
||||||
# ------------------------------------------
|
|
||||||
# 4. Install systemd service
|
|
||||||
# ------------------------------------------
|
|
||||||
|
|
||||||
echo
|
# ============================================================
|
||||||
echo "[4/6] Installing systemd service"
|
# Validate Python application
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Checking Python source files"
|
||||||
|
|
||||||
|
"$PYTHON" -m compileall \
|
||||||
|
-q \
|
||||||
|
"$INSTALL_DIR/app" \
|
||||||
|
"$INSTALL_DIR/desktop"
|
||||||
|
|
||||||
|
success "Python source validation passed."
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Remove Python cache files created by installer
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
find "$INSTALL_DIR/app" \
|
||||||
|
"$INSTALL_DIR/desktop" \
|
||||||
|
-type d \
|
||||||
|
-name "__pycache__" \
|
||||||
|
-prune \
|
||||||
|
-exec rm -rf {} + \
|
||||||
|
2>/dev/null || true
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Restore source ownership
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Fixing source ownership"
|
||||||
|
|
||||||
|
chown -R \
|
||||||
|
"$INSTALL_USER:$INSTALL_GROUP" \
|
||||||
|
"$INSTALL_DIR"
|
||||||
|
|
||||||
|
success "Source tree owned by $INSTALL_USER."
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Prepare data directory
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Preparing application data"
|
||||||
|
|
||||||
|
mkdir -p "$INSTALL_DIR/data"
|
||||||
|
|
||||||
|
# Database is owned by the root service.
|
||||||
|
chown root:root "$INSTALL_DIR/data"
|
||||||
|
chmod 700 "$INSTALL_DIR/data"
|
||||||
|
|
||||||
|
if [ -f "$INSTALL_DIR/data/parental-control.db" ]; then
|
||||||
|
chown root:root \
|
||||||
|
"$INSTALL_DIR/data/parental-control.db"
|
||||||
|
|
||||||
|
chmod 600 \
|
||||||
|
"$INSTALL_DIR/data/parental-control.db"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Data directory secured."
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Install systemd service
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Installing systemd service"
|
||||||
|
|
||||||
|
SERVICE_TEMP="$(mktemp)"
|
||||||
|
|
||||||
sed \
|
sed \
|
||||||
"s|%INSTALL_DIR%|$INSTALL_DIR|g" \
|
"s|%INSTALL_DIR%|$INSTALL_DIR|g" \
|
||||||
"$INSTALL_DIR/parental-control.service" \
|
"$INSTALL_DIR/parental-control.service" \
|
||||||
> "$SERVICE_FILE"
|
> "$SERVICE_TEMP"
|
||||||
|
|
||||||
chmod 644 "$SERVICE_FILE"
|
# Ensure the service does not write Python bytecode into the
|
||||||
|
# Git repository.
|
||||||
|
|
||||||
systemctl daemon-reload
|
if ! grep -q '^Environment=PYTHONDONTWRITEBYTECODE=' "$SERVICE_TEMP"; then
|
||||||
systemctl enable parental-control.service
|
sed -i \
|
||||||
systemctl restart parental-control.service
|
'/^\[Service\]/a Environment=PYTHONDONTWRITEBYTECODE=1' \
|
||||||
|
"$SERVICE_TEMP"
|
||||||
|
fi
|
||||||
|
|
||||||
echo
|
install \
|
||||||
echo "Systemd service installed and started."
|
-o root \
|
||||||
|
-g root \
|
||||||
|
-m 644 \
|
||||||
|
"$SERVICE_TEMP" \
|
||||||
|
"$SERVICE_FILE"
|
||||||
|
|
||||||
# ------------------------------------------
|
rm -f "$SERVICE_TEMP"
|
||||||
# 5. Install desktop application
|
|
||||||
# ------------------------------------------
|
|
||||||
|
|
||||||
echo
|
success "Systemd service installed."
|
||||||
echo "[5/6] Installing application launcher"
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Install polkit policy
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Checking polkit policy"
|
||||||
|
|
||||||
|
if [ -f "$POLKIT_SOURCE" ]; then
|
||||||
|
|
||||||
|
mkdir -p /etc/polkit-1/actions
|
||||||
|
|
||||||
|
install \
|
||||||
|
-o root \
|
||||||
|
-g root \
|
||||||
|
-m 644 \
|
||||||
|
"$POLKIT_SOURCE" \
|
||||||
|
"$POLKIT_DEST"
|
||||||
|
|
||||||
|
success "Polkit policy installed."
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
warning "Polkit policy file is not present yet."
|
||||||
|
warning "Expected: $POLKIT_SOURCE"
|
||||||
|
warning "Skipping polkit policy installation."
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Install desktop launcher
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Installing desktop launcher"
|
||||||
|
|
||||||
cat > "$DESKTOP_FILE" <<EOF
|
cat > "$DESKTOP_FILE" <<EOF
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=$APP_NAME
|
Name=$APP_NAME
|
||||||
Comment=Linux Parental Control Manager
|
Comment=Linux Parental Control Manager
|
||||||
Exec=$INSTALL_DIR/.venv/bin/python -m desktop.main
|
Exec=$PYTHON -m desktop.main
|
||||||
Path=$INSTALL_DIR
|
Path=$INSTALL_DIR
|
||||||
Icon=preferences-system-parental-controls
|
Icon=preferences-system-parental-controls
|
||||||
Terminal=false
|
Terminal=false
|
||||||
@@ -115,27 +530,231 @@ EOF
|
|||||||
|
|
||||||
chmod 644 "$DESKTOP_FILE"
|
chmod 644 "$DESKTOP_FILE"
|
||||||
|
|
||||||
echo
|
success "Desktop launcher installed."
|
||||||
echo "Desktop launcher installed:"
|
|
||||||
echo " $DESKTOP_FILE"
|
|
||||||
|
|
||||||
# ------------------------------------------
|
|
||||||
# 6. Update desktop database
|
|
||||||
# ------------------------------------------
|
|
||||||
|
|
||||||
echo
|
# ============================================================
|
||||||
echo "[6/6] Updating application database"
|
# Validate desktop launcher
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
if command -v desktop-file-validate >/dev/null 2>&1; then
|
||||||
update-desktop-database /usr/share/applications
|
|
||||||
else
|
info "Validating desktop launcher"
|
||||||
echo "update-desktop-database is not installed."
|
|
||||||
echo "Skipping desktop database update."
|
if ! desktop-file-validate "$DESKTOP_FILE"; then
|
||||||
|
error "Desktop launcher validation failed."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ------------------------------------------
|
success "Desktop launcher is valid."
|
||||||
# Verify installation
|
|
||||||
# ------------------------------------------
|
else
|
||||||
|
warning "desktop-file-validate is not available."
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Update desktop database
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||||
|
|
||||||
|
update-desktop-database \
|
||||||
|
/usr/share/applications \
|
||||||
|
>/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Reload systemd
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Reloading systemd"
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
success "systemd reloaded."
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Remove stale IPC socket
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Cleaning stale IPC socket"
|
||||||
|
|
||||||
|
if [ -e "$SOCKET_PATH" ]; then
|
||||||
|
|
||||||
|
if [ -S "$SOCKET_PATH" ]; then
|
||||||
|
rm -f "$SOCKET_PATH"
|
||||||
|
success "Removed stale IPC socket."
|
||||||
|
else
|
||||||
|
warning "$SOCKET_PATH exists but is not a socket."
|
||||||
|
warning "Leaving it untouched."
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Enable service
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Enabling parental control service"
|
||||||
|
|
||||||
|
systemctl enable "$SERVICE_NAME"
|
||||||
|
|
||||||
|
success "Service enabled."
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Restart service
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Starting parental control service"
|
||||||
|
|
||||||
|
systemctl restart "$SERVICE_NAME"
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Wait for service
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
SERVICE_READY=0
|
||||||
|
|
||||||
|
for _ in $(seq 1 15); do
|
||||||
|
|
||||||
|
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||||
|
SERVICE_READY=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
if [ "$SERVICE_READY" -ne 1 ]; then
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Service startup failed"
|
||||||
|
echo "=========================================="
|
||||||
|
echo
|
||||||
|
|
||||||
|
systemctl status \
|
||||||
|
"$SERVICE_NAME" \
|
||||||
|
--no-pager || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Recent service logs:"
|
||||||
|
echo
|
||||||
|
|
||||||
|
journalctl \
|
||||||
|
-u "$SERVICE_NAME" \
|
||||||
|
-n 50 \
|
||||||
|
--no-pager || true
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Service is running."
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Verify IPC socket
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Checking IPC socket"
|
||||||
|
|
||||||
|
if [ ! -S "$SOCKET_PATH" ]; then
|
||||||
|
|
||||||
|
warning "IPC socket was not created:"
|
||||||
|
warning "$SOCKET_PATH"
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
success "IPC socket is active."
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Verify database
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Checking database"
|
||||||
|
|
||||||
|
if [ -f "$INSTALL_DIR/data/parental-control.db" ]; then
|
||||||
|
|
||||||
|
DB_OWNER="$(stat -c '%U:%G' "$INSTALL_DIR/data/parental-control.db")"
|
||||||
|
DB_MODE="$(stat -c '%a' "$INSTALL_DIR/data/parental-control.db")"
|
||||||
|
|
||||||
|
echo " Owner: $DB_OWNER"
|
||||||
|
echo " Permissions: $DB_MODE"
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
warning "Database does not exist yet."
|
||||||
|
warning "The service should create it automatically."
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Final verification
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
info "Running final checks"
|
||||||
|
|
||||||
|
CHECK_FAILED=0
|
||||||
|
|
||||||
|
|
||||||
|
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||||
|
echo " Service: OK"
|
||||||
|
else
|
||||||
|
echo " Service: FAILED"
|
||||||
|
CHECK_FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if [ -x "$PYTHON" ]; then
|
||||||
|
echo " Python: OK"
|
||||||
|
else
|
||||||
|
echo " Python: FAILED"
|
||||||
|
CHECK_FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if [ -f "$DESKTOP_FILE" ]; then
|
||||||
|
echo " Desktop: OK"
|
||||||
|
else
|
||||||
|
echo " Desktop: FAILED"
|
||||||
|
CHECK_FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if [ -S "$SOCKET_PATH" ]; then
|
||||||
|
echo " IPC: OK"
|
||||||
|
else
|
||||||
|
echo " IPC: WARNING"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if [ -f "$POLKIT_DEST" ]; then
|
||||||
|
echo " Polkit: OK"
|
||||||
|
else
|
||||||
|
echo " Polkit: NOT INSTALLED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if [ "$CHECK_FAILED" -ne 0 ]; then
|
||||||
|
error "One or more required installation checks failed."
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Finished
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
@@ -145,11 +764,24 @@ echo
|
|||||||
echo "Application:"
|
echo "Application:"
|
||||||
echo " $APP_NAME"
|
echo " $APP_NAME"
|
||||||
echo
|
echo
|
||||||
echo "Launch it from your application menu."
|
echo "Launch:"
|
||||||
echo "On your system:"
|
echo " Application menu"
|
||||||
|
echo " Rofi"
|
||||||
|
echo
|
||||||
|
echo "Rofi:"
|
||||||
echo " Super + D"
|
echo " Super + D"
|
||||||
echo
|
echo
|
||||||
echo "Service:"
|
echo "Service:"
|
||||||
echo " systemctl status parental-control"
|
echo " systemctl status $SERVICE_NAME"
|
||||||
|
echo
|
||||||
|
echo "Logs:"
|
||||||
|
echo " journalctl -u $SERVICE_NAME"
|
||||||
|
echo
|
||||||
|
echo "IPC:"
|
||||||
|
echo " $SOCKET_PATH"
|
||||||
|
echo
|
||||||
|
echo "Installation:"
|
||||||
|
echo " $INSTALL_DIR"
|
||||||
echo
|
echo
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
|
echo
|
||||||
|
|||||||
@@ -1,4 +1 @@
|
|||||||
PySide6==6.11.2
|
PySide6==6.11.2
|
||||||
PySide6_Addons==6.11.2
|
|
||||||
PySide6_Essentials==6.11.2
|
|
||||||
shiboken6==6.11.2
|
|
||||||
|
|||||||
Reference in New Issue
Block a user