Initial release: RadiantWave v1.0
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
// Allow kiosk user to run apt update, apt install for rhemawave packages,
|
||||
// and restart getty for application reloading
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "org.freedesktop.policykit.exec" &&
|
||||
subject.user == "kiosk") {
|
||||
|
||||
var program = action.lookup("program");
|
||||
var cmdline = action.lookup("command_line");
|
||||
|
||||
// Allow dpkg --configure -a
|
||||
if (program == "/usr/bin/dpkg") {
|
||||
if (cmdline && cmdline.indexOf("--configure") >= 0) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow apt update
|
||||
if (program == "/usr/bin/apt") {
|
||||
if (cmdline && cmdline.indexOf("update") >= 0) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
|
||||
// Allow: apt install --only-upgrade rhemawave[-dev]
|
||||
if (cmdline &&
|
||||
cmdline.indexOf("install") >= 0 &&
|
||||
cmdline.indexOf("--only-upgrade") >= 0 &&
|
||||
cmdline.indexOf("rhemawave") >= 0) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow systemctl restart getty@tty1.service
|
||||
if (program == "/usr/bin/systemctl") {
|
||||
if (cmdline &&
|
||||
cmdline.indexOf("restart") >= 0 &&
|
||||
cmdline.indexOf("getty@tty1.service") >= 0) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow sh for Tailscale install script
|
||||
if (program == "/usr/bin/sh" || program == "/bin/sh") {
|
||||
if (cmdline && cmdline.indexOf("tailscale.com/install.sh") >= 0) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow tailscale set --operator
|
||||
if (program == "/usr/bin/tailscale") {
|
||||
if (cmdline && cmdline.indexOf("set") >= 0 && cmdline.indexOf("--operator") >= 0) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/sbin/agetty --autologin kiosk --noclear %I $TERM
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# Post-installation script for RhemaWave
|
||||
set -e
|
||||
|
||||
# Create kiosk group if it doesn't exist
|
||||
if ! getent group kiosk > /dev/null 2>&1; then
|
||||
echo "Creating kiosk group..."
|
||||
groupadd --system kiosk
|
||||
fi
|
||||
|
||||
# Create kiosk user if it doesn't exist
|
||||
if ! getent passwd kiosk > /dev/null 2>&1; then
|
||||
echo "Creating kiosk user..."
|
||||
useradd --system --gid kiosk --create-home --home-dir /home/kiosk \
|
||||
--shell /bin/bash --comment "RhemaWave Kiosk User" kiosk
|
||||
fi
|
||||
|
||||
# Set proper group ownership for writable directories
|
||||
chgrp -R kiosk /usr/local/share/rhemawave
|
||||
|
||||
# Ensure kiosk user can write to the data directory
|
||||
chmod g+w /usr/local/share/rhemawave
|
||||
|
||||
echo "✓ RhemaWave post-install complete"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
# RhemaWave kiosk auto-launch on tty1
|
||||
if [ -z "$WAYLAND_DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
|
||||
export XDG_RUNTIME_DIR=/run/user/$(id -u)
|
||||
export SDL_VIDEODRIVER=wayland
|
||||
|
||||
# Check for updates before launching
|
||||
/usr/bin/python3 /usr/local/bin/rhemawave-updater.py || true
|
||||
|
||||
# Set all sinks to full volume
|
||||
for sink in $(wpctl status | grep -oP '^\s+\K\d+(?=\.)' | head -10); do
|
||||
wpctl set-volume "$sink" 1.0 2>/dev/null
|
||||
done
|
||||
|
||||
exec cage -- /usr/local/bin/rhemawave
|
||||
fi
|
||||
|
||||
Executable
+564
@@ -0,0 +1,564 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
rhemawave-updater.py
|
||||
|
||||
Auto-updater for RhemaWave kiosk application.
|
||||
Uses apt to check for and install updates from the configured repository.
|
||||
|
||||
USAGE:
|
||||
Run as kiosk user:
|
||||
python3 /usr/local/bin/rhemawave-updater.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import json
|
||||
import socket
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# Configuration - templated by build script
|
||||
PACKAGE_NAME = "__CHANNEL__"
|
||||
HEADSCALE_URL = "https://headscale.rhemawavetech.com"
|
||||
HEADSCALE_AUTHKEY = "803130d5b2e38bffa9fa5b6921a3d6403100e2e30fea1bd5"
|
||||
|
||||
# Paths
|
||||
LOG_FILE = Path("/home/kiosk/rhemawave-updater.log")
|
||||
LICENSE_KEY_PATH = Path.home() / ".local/share/rhemawave/license_key"
|
||||
|
||||
|
||||
class UpdaterLogger:
|
||||
"""Simple logger that writes to both stdout and file"""
|
||||
|
||||
def __init__(self, log_file):
|
||||
self.log_file = log_file
|
||||
log_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
log_file.touch(exist_ok=True)
|
||||
|
||||
def log(self, message):
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
formatted = f"[{timestamp}] {message}"
|
||||
print(formatted)
|
||||
try:
|
||||
with open(self.log_file, 'a') as f:
|
||||
f.write(formatted + '\n')
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
def error(self, message):
|
||||
self.log(f"ERROR: {message}")
|
||||
|
||||
def debug(self, message):
|
||||
self.log(f"DEBUG: {message}")
|
||||
|
||||
def success(self, message):
|
||||
self.log(f"✓ {message}")
|
||||
|
||||
|
||||
logger = UpdaterLogger(LOG_FILE)
|
||||
|
||||
|
||||
def check_network(timeout=2):
|
||||
"""Quick check for internet connectivity."""
|
||||
logger.log("Checking network connectivity...")
|
||||
try:
|
||||
socket.create_connection(("1.1.1.1", 53), timeout=timeout)
|
||||
logger.success("Network check passed (1.1.1.1:53 reachable)")
|
||||
return True
|
||||
except OSError as e:
|
||||
logger.error(f"Network check failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def run_command(cmd, description, use_pkexec=False):
|
||||
"""Run a command and return success status"""
|
||||
if use_pkexec:
|
||||
cmd = ["pkexec"] + cmd
|
||||
|
||||
logger.log(f"{description}...")
|
||||
logger.debug(f"Command: {' '.join(cmd)}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300
|
||||
)
|
||||
|
||||
logger.debug(f"Return code: {result.returncode}")
|
||||
|
||||
if result.stdout:
|
||||
for line in result.stdout.splitlines():
|
||||
logger.log(f" {line}")
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.error(f"{description} failed with return code {result.returncode}")
|
||||
if result.stderr:
|
||||
for line in result.stderr.splitlines():
|
||||
logger.error(f" {line}")
|
||||
return False
|
||||
|
||||
logger.success(f"{description} completed successfully")
|
||||
return True
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error(f"{description} timed out")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"{description} failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def configure_dpkg():
|
||||
logger.log("Checking for dpkg errors")
|
||||
if not run_command(
|
||||
["dpkg", "--configure", "-a"],
|
||||
'Check for dpkg configuration errors and resolve them before performing updates',
|
||||
use_pkexec=True):
|
||||
logger.error("Unable to reconfigure system, please reach out to support!")
|
||||
return
|
||||
|
||||
|
||||
def is_tailscale_installed():
|
||||
"""Check if Tailscale is installed."""
|
||||
logger.log("Checking if Tailscale is installed...")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["tailscale", "version"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
version = result.stdout.strip()
|
||||
logger.success(f"Tailscale is installed: {version}")
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
logger.log("Tailscale binary not found")
|
||||
return False
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"Tailscale version check failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def install_tailscale():
|
||||
"""Install Tailscale."""
|
||||
logger.log("Installing Tailscale...")
|
||||
try:
|
||||
logger.debug("Running: pkexec sh -c 'curl -fsSL https://tailscale.com/install.sh | sh'")
|
||||
result = subprocess.run(
|
||||
["pkexec", "sh", "-c", "curl -fsSL https://tailscale.com/install.sh | sh"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120
|
||||
)
|
||||
logger.debug(f"Install return code: {result.returncode}")
|
||||
if result.stdout:
|
||||
for line in result.stdout.splitlines():
|
||||
logger.log(f" {line}")
|
||||
if result.stderr:
|
||||
for line in result.stderr.splitlines():
|
||||
logger.log(f" [stderr] {line}")
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.error("Tailscale installation failed")
|
||||
return False
|
||||
|
||||
logger.success("Tailscale installed successfully")
|
||||
return True
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error("Tailscale installation timed out")
|
||||
return False
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"Tailscale installation failed: {e}")
|
||||
if e.stderr:
|
||||
logger.error(f"stderr: {e.stderr}")
|
||||
return False
|
||||
|
||||
|
||||
def set_tailscale_operator():
|
||||
"""Set Tailscale operator to kiosk."""
|
||||
logger.log("Setting Tailscale operator to 'kiosk'...")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["pkexec", "tailscale", "set", "--operator=kiosk"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
logger.debug(f"Return code: {result.returncode}")
|
||||
if result.stdout:
|
||||
logger.debug(f"stdout: {result.stdout}")
|
||||
if result.stderr:
|
||||
logger.debug(f"stderr: {result.stderr}")
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.error(f"Failed to set operator: {result.stderr}")
|
||||
return False
|
||||
|
||||
logger.success("Tailscale operator set to 'kiosk'")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to set Tailscale operator: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_tailscale_status():
|
||||
"""Get full Tailscale status as dict."""
|
||||
logger.log("Getting Tailscale status...")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["tailscale", "status", "--json"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
logger.debug(f"Return code: {result.returncode}")
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.error(f"tailscale status failed: {result.stderr}")
|
||||
return None
|
||||
|
||||
status = json.loads(result.stdout)
|
||||
|
||||
# Log key status info
|
||||
backend_state = status.get("BackendState", "unknown")
|
||||
logger.log(f" BackendState: {backend_state}")
|
||||
|
||||
self_info = status.get("Self", {})
|
||||
if self_info:
|
||||
logger.log(f" HostName: {self_info.get('HostName', 'unknown')}")
|
||||
logger.log(f" DNSName: {self_info.get('DNSName', 'unknown')}")
|
||||
logger.log(f" Online: {self_info.get('Online', 'unknown')}")
|
||||
tailscale_ips = self_info.get("TailscaleIPs", [])
|
||||
logger.log(f" TailscaleIPs: {tailscale_ips}")
|
||||
|
||||
# Check for health issues
|
||||
health = status.get("Health", [])
|
||||
if health:
|
||||
logger.log(" Health issues:")
|
||||
for issue in health:
|
||||
logger.log(f" - {issue}")
|
||||
|
||||
return status
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Failed to parse Tailscale status JSON: {e}")
|
||||
return None
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error("Tailscale status timed out")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get Tailscale status: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def is_tailscale_logged_in(status=None):
|
||||
"""Check if Tailscale is logged in."""
|
||||
if status is None:
|
||||
status = get_tailscale_status()
|
||||
|
||||
if status is None:
|
||||
logger.error("Cannot determine login status - no status available")
|
||||
return False
|
||||
|
||||
backend_state = status.get("BackendState", "")
|
||||
logged_in = backend_state == "Running"
|
||||
|
||||
if logged_in:
|
||||
logger.success("Tailscale is logged in (BackendState=Running)")
|
||||
else:
|
||||
logger.log(f"Tailscale is NOT logged in (BackendState={backend_state})")
|
||||
|
||||
return logged_in
|
||||
|
||||
|
||||
def tailscale_login(hostname=None):
|
||||
"""Log in to Tailscale/Headscale."""
|
||||
logger.log("Logging in to Headscale...")
|
||||
logger.debug(f" Server: {HEADSCALE_URL}")
|
||||
logger.debug(f" AuthKey: {HEADSCALE_AUTHKEY[:8]}...{HEADSCALE_AUTHKEY[-4:]}")
|
||||
if hostname:
|
||||
logger.debug(f" Hostname: {hostname}")
|
||||
|
||||
cmd = [
|
||||
"tailscale", "up",
|
||||
"--login-server", HEADSCALE_URL,
|
||||
"--authkey", HEADSCALE_AUTHKEY,
|
||||
"--force-reauth"
|
||||
]
|
||||
if hostname:
|
||||
cmd.extend(["--hostname", hostname])
|
||||
|
||||
logger.debug(f"Command: {' '.join(cmd)}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
logger.debug(f"Return code: {result.returncode}")
|
||||
if result.stdout:
|
||||
logger.log(f" stdout: {result.stdout}")
|
||||
if result.stderr:
|
||||
logger.log(f" stderr: {result.stderr}")
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.error(f"Tailscale login failed with code {result.returncode}")
|
||||
return False
|
||||
|
||||
logger.success("Tailscale login successful")
|
||||
return True
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error("Tailscale login timed out")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Tailscale login failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_current_tailscale_hostname(status=None):
|
||||
"""Get the hostname we're currently advertising."""
|
||||
if status is None:
|
||||
status = get_tailscale_status()
|
||||
|
||||
if status is None:
|
||||
return None
|
||||
|
||||
hostname = status.get("Self", {}).get("HostName")
|
||||
logger.debug(f"Current Tailscale hostname: {hostname}")
|
||||
return hostname
|
||||
|
||||
|
||||
def get_license_key():
|
||||
"""Read license key from plain text file."""
|
||||
logger.log("Fetching license key...")
|
||||
logger.debug(f" Path: {LICENSE_KEY_PATH}")
|
||||
|
||||
if not LICENSE_KEY_PATH.exists():
|
||||
logger.log(" License key file does not exist")
|
||||
return None
|
||||
|
||||
try:
|
||||
license_key = LICENSE_KEY_PATH.read_text().strip()
|
||||
if not license_key:
|
||||
logger.log(" License key file is empty")
|
||||
return None
|
||||
|
||||
logger.success(f"License key found: {license_key}")
|
||||
return license_key
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to read license key: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def ensure_tailscale_installed():
|
||||
"""Ensure Tailscale is installed, install if needed."""
|
||||
logger.log("=== Ensuring Tailscale is installed ===")
|
||||
|
||||
if is_tailscale_installed():
|
||||
return True
|
||||
|
||||
logger.log("Tailscale not installed, attempting installation...")
|
||||
|
||||
if not install_tailscale():
|
||||
return False
|
||||
|
||||
# Verify installation
|
||||
if not is_tailscale_installed():
|
||||
logger.error("Tailscale installation verification failed")
|
||||
return False
|
||||
|
||||
# Set operator
|
||||
if not set_tailscale_operator():
|
||||
logger.error("Failed to set Tailscale operator, continuing anyway...")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def ensure_tailscale_connected():
|
||||
"""Ensure Tailscale is logged in and connected."""
|
||||
logger.log("=== Ensuring Tailscale is connected ===")
|
||||
|
||||
status = get_tailscale_status()
|
||||
|
||||
if not is_tailscale_logged_in(status):
|
||||
logger.log("Tailscale not logged in, attempting login...")
|
||||
|
||||
# Get license key to use as hostname if available
|
||||
license_key = get_license_key()
|
||||
|
||||
if not tailscale_login(hostname=license_key):
|
||||
logger.error("Failed to log in to Tailscale")
|
||||
return False
|
||||
|
||||
# Re-check status after login
|
||||
status = get_tailscale_status()
|
||||
if not is_tailscale_logged_in(status):
|
||||
logger.error("Still not logged in after login attempt")
|
||||
return False
|
||||
|
||||
logger.success("Tailscale is connected")
|
||||
return True
|
||||
|
||||
|
||||
def sync_tailscale_hostname():
|
||||
"""Update tailscale hostname to match license key if needed."""
|
||||
logger.log("=== Syncing Tailscale hostname ===")
|
||||
|
||||
license_key = get_license_key()
|
||||
|
||||
if not license_key:
|
||||
logger.log("No license key found, skipping hostname sync")
|
||||
return
|
||||
|
||||
status = get_tailscale_status()
|
||||
current = get_current_tailscale_hostname(status)
|
||||
|
||||
logger.log(f" Current hostname: {current}")
|
||||
logger.log(f" Desired hostname: {license_key}")
|
||||
|
||||
if current == license_key:
|
||||
logger.success(f"Hostname already correct: {license_key}")
|
||||
return
|
||||
|
||||
logger.log(f"Hostname mismatch, updating: {current} -> {license_key}")
|
||||
|
||||
if tailscale_login(hostname=license_key):
|
||||
logger.success("Hostname updated successfully")
|
||||
# Verify the change
|
||||
new_status = get_tailscale_status()
|
||||
new_hostname = get_current_tailscale_hostname(new_status)
|
||||
logger.log(f" Verified new hostname: {new_hostname}")
|
||||
else:
|
||||
logger.error("Failed to update hostname")
|
||||
|
||||
|
||||
def sync_ssh_keys():
|
||||
"""Fetch SSH public keys from GitHub and write to authorized_keys."""
|
||||
logger.log("=== Syncing SSH keys from GitHub ===")
|
||||
github_user = "nclack"
|
||||
url = f"https://github.com/{github_user}.keys"
|
||||
|
||||
try:
|
||||
logger.debug(f"Fetching keys from {url}")
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "rhemawave-updater"})
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
keys = resp.read().decode().strip()
|
||||
|
||||
if not keys:
|
||||
logger.error("No SSH keys returned from GitHub")
|
||||
return
|
||||
|
||||
ssh_dir = Path.home() / ".ssh"
|
||||
ssh_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
auth_keys = ssh_dir / "authorized_keys"
|
||||
auth_keys.write_text(keys + "\n")
|
||||
auth_keys.chmod(0o600)
|
||||
|
||||
key_count = len(keys.splitlines())
|
||||
logger.success(f"Wrote {key_count} SSH key(s) to {auth_keys}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to sync SSH keys: {e}")
|
||||
|
||||
|
||||
def check_for_upgrade():
|
||||
"""Check if an upgrade is available for the package"""
|
||||
logger.log(f"Checking for upgrades to {PACKAGE_NAME}...")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["apt", "list", "--upgradable"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
logger.debug(f"apt list output: {result.stdout}")
|
||||
|
||||
if PACKAGE_NAME in result.stdout:
|
||||
logger.success(f"Upgrade available for {PACKAGE_NAME}")
|
||||
return True
|
||||
else:
|
||||
logger.log(f"{PACKAGE_NAME} is up to date")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Could not check for upgrades: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
logger.log("=========================================")
|
||||
logger.log("RhemaWave Updater")
|
||||
logger.log(f"Package: {PACKAGE_NAME}")
|
||||
logger.log(f"Timestamp: {datetime.now().isoformat()}")
|
||||
logger.log("=========================================")
|
||||
|
||||
# Quick network check
|
||||
if not check_network():
|
||||
logger.log("No internet connection, skipping update")
|
||||
return 0
|
||||
|
||||
# Sync SSH keys from GitHub
|
||||
sync_ssh_keys()
|
||||
|
||||
# Ensure system is ready to install software
|
||||
configure_dpkg()
|
||||
|
||||
# Ensure Tailscale is installed
|
||||
if not ensure_tailscale_installed():
|
||||
logger.error("Tailscale installation failed, continuing with update check...")
|
||||
else:
|
||||
# Ensure Tailscale is connected
|
||||
if not ensure_tailscale_connected():
|
||||
logger.error("Tailscale connection failed, continuing with update check...")
|
||||
else:
|
||||
# Sync hostname with license key
|
||||
sync_tailscale_hostname()
|
||||
|
||||
logger.log("=========================================")
|
||||
logger.log("=== Package Update Check ===")
|
||||
logger.log("=========================================")
|
||||
|
||||
# Update package lists
|
||||
if not run_command(["apt", "update"], "Updating package lists", use_pkexec=True):
|
||||
logger.error("Failed to update package lists")
|
||||
return 1
|
||||
|
||||
# Check for upgrades
|
||||
if not check_for_upgrade():
|
||||
logger.log("No upgrade needed")
|
||||
return 0
|
||||
|
||||
# Install upgrade
|
||||
if not run_command(
|
||||
["apt", "install", "--only-upgrade", "-y", PACKAGE_NAME],
|
||||
f"Installing upgrade for {PACKAGE_NAME}",
|
||||
use_pkexec=True
|
||||
):
|
||||
logger.error("Upgrade installation failed")
|
||||
return 1
|
||||
|
||||
logger.success("Upgrade installed successfully!")
|
||||
|
||||
# Restart getty to reload the application
|
||||
if not run_command(
|
||||
["systemctl", "restart", "getty@tty1.service"],
|
||||
"Restarting application",
|
||||
use_pkexec=True
|
||||
):
|
||||
logger.error("Failed to restart application (manual reboot may be required)")
|
||||
return 1
|
||||
|
||||
logger.success("Application restarted")
|
||||
logger.log("=========================================")
|
||||
logger.log("=== Update Complete ===")
|
||||
logger.log("=========================================")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user