325 lines
10 KiB
Bash
Executable File
325 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_PATH="${BASH_SOURCE[0]:-${0}}"
|
|
SCRIPT_DIR="$(cd "$(dirname "${SCRIPT_PATH}")" && pwd)"
|
|
TAURI_DIR="${SCRIPT_DIR}/ui-tauri-react/src-tauri"
|
|
BUILD_SCRIPT="${SCRIPT_DIR}/build-rust-runtime.sh"
|
|
RUNTIME_INSTALL_DIR="/usr/local/libexec/zenbook-duo"
|
|
RUNTIME_BINS=(
|
|
zenbook-duo-daemon
|
|
zenbook-duo-session-agent
|
|
zenbook-duo-lifecycle
|
|
zenbook-duo-usb-remap-helper
|
|
)
|
|
|
|
BUILD_ONLY=false
|
|
INSTALL_ONLY=false
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
install-rust-runtime.sh - build and/or install Zenbook Duo background services
|
|
|
|
Usage:
|
|
./install-rust-runtime.sh # build, then install (default)
|
|
./install-rust-runtime.sh --build-only # compile binaries only
|
|
./install-rust-runtime.sh --install-only # install existing binaries + systemd units
|
|
|
|
Build output: ui-tauri-react/src-tauri/target/release/
|
|
Install path: /usr/local/libexec/zenbook-duo/
|
|
EOF
|
|
}
|
|
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
--build-only)
|
|
BUILD_ONLY=true
|
|
shift
|
|
;;
|
|
--install-only)
|
|
INSTALL_ONLY=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "ERROR: unknown argument: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ "${BUILD_ONLY}" = true ] && [ "${INSTALL_ONLY}" = true ]; then
|
|
echo "ERROR: Choose only one of --build-only or --install-only" >&2
|
|
exit 1
|
|
fi
|
|
SYSTEM_SERVICE_NAME="zenbook-duo-rust-daemon.service"
|
|
LIFECYCLE_SERVICE_NAME="zenbook-duo-rust-lifecycle.service"
|
|
USER_SERVICE_NAME="zenbook-duo-session-agent.service"
|
|
SYSTEM_SLEEP_HOOK_PATH="/usr/lib/systemd/system-sleep/zenbook-duo-rust-lifecycle"
|
|
SYSTEM_SLEEP_HOOK_DIR="$(dirname "${SYSTEM_SLEEP_HOOK_PATH}")"
|
|
|
|
TARGET_USER="${USER:-}"
|
|
if [ "${EUID}" = "0" ]; then
|
|
if [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then
|
|
TARGET_USER="${SUDO_USER}"
|
|
else
|
|
echo "ERROR: install-rust-runtime.sh must be launched from a real user session." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
TARGET_UID="$(id -u "${TARGET_USER}" 2>/dev/null || true)"
|
|
if [ -z "${TARGET_UID}" ]; then
|
|
echo "ERROR: failed to resolve target user '${TARGET_USER}'" >&2
|
|
exit 1
|
|
fi
|
|
TARGET_GID="$(id -g "${TARGET_USER}" 2>/dev/null || true)"
|
|
if [ -z "${TARGET_GID}" ]; then
|
|
echo "ERROR: failed to resolve primary group for '${TARGET_USER}'" >&2
|
|
exit 1
|
|
fi
|
|
TARGET_HOME="$(getent passwd "${TARGET_USER}" 2>/dev/null | cut -d: -f6)"
|
|
if [ -z "${TARGET_HOME}" ]; then
|
|
echo "ERROR: failed to resolve home directory for '${TARGET_USER}'" >&2
|
|
exit 1
|
|
fi
|
|
|
|
die() {
|
|
echo "ERROR: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
emit_systemctl_failure() {
|
|
local manager="$1"
|
|
local command_display="$2"
|
|
local output="${3:-}"
|
|
|
|
echo "ERROR: ${manager} systemd manager is unavailable; cannot run: ${command_display}" >&2
|
|
if [ -n "${output}" ]; then
|
|
printf '%s\n' "${output}" | sed 's/^/ systemctl: /' >&2
|
|
fi
|
|
}
|
|
|
|
run_system_systemctl() {
|
|
local output=""
|
|
if ! output="$(sudo systemctl --system "$@" 2>&1)"; then
|
|
emit_systemctl_failure "The system" "sudo systemctl --system $*" "${output}"
|
|
echo "Make sure this machine is booted with systemd and that 'sudo systemctl status' works, then rerun ./install.sh." >&2
|
|
exit 1
|
|
fi
|
|
if [ -n "${output}" ]; then
|
|
printf '%s\n' "${output}"
|
|
fi
|
|
}
|
|
|
|
run_user_systemctl() {
|
|
local runtime_dir="/run/user/${TARGET_UID}"
|
|
local bus_address="unix:path=${runtime_dir}/bus"
|
|
|
|
if [ "${TARGET_USER}" = "${USER:-}" ] && [ "${EUID}" != "0" ]; then
|
|
XDG_RUNTIME_DIR="${runtime_dir}" \
|
|
DBUS_SESSION_BUS_ADDRESS="${bus_address}" \
|
|
systemctl --user "$@"
|
|
return
|
|
fi
|
|
|
|
sudo -u "${TARGET_USER}" \
|
|
XDG_RUNTIME_DIR="${runtime_dir}" \
|
|
DBUS_SESSION_BUS_ADDRESS="${bus_address}" \
|
|
systemctl --user "$@"
|
|
}
|
|
|
|
run_user_systemctl_checked() {
|
|
local output=""
|
|
if ! output="$(run_user_systemctl "$@" 2>&1)"; then
|
|
emit_systemctl_failure "The ${TARGET_USER} user" "systemctl --user $*" "${output}"
|
|
echo "Run the installer from an active desktop login where 'systemctl --user status' works, then rerun ./install.sh." >&2
|
|
exit 1
|
|
fi
|
|
if [ -n "${output}" ]; then
|
|
printf '%s\n' "${output}"
|
|
fi
|
|
}
|
|
|
|
ensure_system_manager_available() {
|
|
local output=""
|
|
if ! output="$(sudo systemctl --system show --property=Version --value 2>&1)"; then
|
|
emit_systemctl_failure "The system" "sudo systemctl --system show --property=Version --value" "${output}"
|
|
echo "Make sure this machine is booted with systemd and that 'sudo systemctl status' works, then rerun ./install.sh." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
ensure_user_manager_available() {
|
|
local runtime_dir="/run/user/${TARGET_UID}"
|
|
local bus_path="${runtime_dir}/bus"
|
|
local output=""
|
|
|
|
if [ ! -d "${runtime_dir}" ]; then
|
|
die "The systemd user runtime directory is missing for ${TARGET_USER}: ${runtime_dir}. Log in as ${TARGET_USER} and rerun ./install.sh from that desktop session."
|
|
fi
|
|
|
|
if [ ! -S "${bus_path}" ]; then
|
|
die "The systemd user bus is missing for ${TARGET_USER}: ${bus_path}. Log in as ${TARGET_USER} and rerun ./install.sh from that desktop session."
|
|
fi
|
|
|
|
if ! output="$(run_user_systemctl show-environment 2>&1)"; then
|
|
emit_systemctl_failure "The ${TARGET_USER} user" "systemctl --user show-environment" "${output}"
|
|
echo "Run the installer from an active desktop login where 'systemctl --user status' works, then rerun ./install.sh." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
import_user_environment() {
|
|
run_user_systemctl import-environment DISPLAY WAYLAND_DISPLAY NIRI_SOCKET XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP DESKTOP_SESSION XDG_SESSION_TYPE >/dev/null 2>&1 || true
|
|
|
|
if command -v dbus-update-activation-environment >/dev/null 2>&1; then
|
|
if [ "${TARGET_USER}" = "${USER:-}" ] && [ "${EUID}" != "0" ]; then
|
|
XDG_RUNTIME_DIR="/run/user/${TARGET_UID}" \
|
|
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${TARGET_UID}/bus" \
|
|
dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY NIRI_SOCKET XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP DESKTOP_SESSION XDG_SESSION_TYPE >/dev/null 2>&1 || true
|
|
else
|
|
sudo -u "${TARGET_USER}" \
|
|
XDG_RUNTIME_DIR="/run/user/${TARGET_UID}" \
|
|
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${TARGET_UID}/bus" \
|
|
dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY NIRI_SOCKET XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP DESKTOP_SESSION XDG_SESSION_TYPE >/dev/null 2>&1 || true
|
|
fi
|
|
fi
|
|
}
|
|
|
|
need_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || {
|
|
echo "ERROR: Missing required command: $1" >&2
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
if [ "${BUILD_ONLY}" = true ]; then
|
|
exec "${BUILD_SCRIPT}"
|
|
fi
|
|
|
|
if [ "${INSTALL_ONLY}" = false ]; then
|
|
"${BUILD_SCRIPT}"
|
|
fi
|
|
|
|
for binary in "${RUNTIME_BINS[@]}"; do
|
|
if [ ! -x "${TAURI_DIR}/target/release/${binary}" ]; then
|
|
die "Missing built binary ${TAURI_DIR}/target/release/${binary}. Run ./build-rust-runtime.sh first."
|
|
fi
|
|
done
|
|
|
|
need_cmd systemctl
|
|
ensure_system_manager_available
|
|
ensure_user_manager_available
|
|
|
|
echo "Installing Rust runtime binaries..."
|
|
sudo mkdir -p "${RUNTIME_INSTALL_DIR}"
|
|
sudo install -m 0755 \
|
|
"${TAURI_DIR}/target/release/zenbook-duo-daemon" \
|
|
"${RUNTIME_INSTALL_DIR}/zenbook-duo-daemon"
|
|
sudo install -m 0755 \
|
|
"${TAURI_DIR}/target/release/zenbook-duo-session-agent" \
|
|
"${RUNTIME_INSTALL_DIR}/zenbook-duo-session-agent"
|
|
sudo install -m 0755 \
|
|
"${TAURI_DIR}/target/release/zenbook-duo-lifecycle" \
|
|
"${RUNTIME_INSTALL_DIR}/zenbook-duo-lifecycle"
|
|
sudo install -m 0755 \
|
|
"${TAURI_DIR}/target/release/zenbook-duo-usb-remap-helper" \
|
|
"${RUNTIME_INSTALL_DIR}/zenbook-duo-usb-remap-helper"
|
|
|
|
echo "Installed Rust runtime versions:"
|
|
for binary in \
|
|
zenbook-duo-daemon \
|
|
zenbook-duo-session-agent \
|
|
zenbook-duo-lifecycle \
|
|
zenbook-duo-usb-remap-helper; do
|
|
"${RUNTIME_INSTALL_DIR}/${binary}" --version || true
|
|
done
|
|
|
|
echo "Installing Rust runtime services..."
|
|
cat <<EOF | sudo tee "/etc/systemd/system/${SYSTEM_SERVICE_NAME}" >/dev/null
|
|
[Unit]
|
|
Description=Zenbook Duo Rust Daemon
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=${RUNTIME_INSTALL_DIR}/zenbook-duo-daemon
|
|
Environment=ZENBOOK_DUO_HOME=${TARGET_HOME}
|
|
Environment=ZENBOOK_DUO_USER=${TARGET_USER}
|
|
Environment=ZENBOOK_DUO_UID=${TARGET_UID}
|
|
Environment=ZENBOOK_DUO_GID=${TARGET_GID}
|
|
Restart=on-failure
|
|
RestartSec=1
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
cat <<EOF | sudo tee "/etc/systemd/system/${LIFECYCLE_SERVICE_NAME}" >/dev/null
|
|
[Unit]
|
|
Description=Zenbook Duo Rust Lifecycle Handler
|
|
DefaultDependencies=no
|
|
Before=shutdown.target
|
|
After=multi-user.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=${RUNTIME_INSTALL_DIR}/zenbook-duo-lifecycle boot
|
|
ExecStop=${RUNTIME_INSTALL_DIR}/zenbook-duo-lifecycle shutdown
|
|
RemainAfterExit=yes
|
|
TimeoutStartSec=10
|
|
TimeoutStopSec=10
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
cat <<EOF | sudo tee "/etc/systemd/user/${USER_SERVICE_NAME}" >/dev/null
|
|
[Unit]
|
|
Description=Zenbook Duo Session Agent
|
|
ConditionUser=!gdm
|
|
After=graphical-session.target
|
|
StartLimitIntervalSec=0
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStartPre=/bin/sh -c 'test -n "\${DISPLAY:-}\${WAYLAND_DISPLAY:-}\${NIRI_SOCKET:-}"'
|
|
ExecStart=${RUNTIME_INSTALL_DIR}/zenbook-duo-session-agent
|
|
Restart=on-failure
|
|
RestartSec=2
|
|
TimeoutStopSec=5
|
|
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
EOF
|
|
|
|
sudo mkdir -p "${SYSTEM_SLEEP_HOOK_DIR}"
|
|
sudo rm -f "${SYSTEM_SLEEP_HOOK_PATH}"
|
|
sudo ln -sfn "${RUNTIME_INSTALL_DIR}/zenbook-duo-lifecycle" "${SYSTEM_SLEEP_HOOK_PATH}"
|
|
|
|
sudo systemctl disable zenbook-duo.service 2>/dev/null || true
|
|
sudo systemctl stop zenbook-duo.service 2>/dev/null || true
|
|
sudo rm -f /usr/lib/systemd/system-sleep/duo
|
|
|
|
run_system_systemctl daemon-reload
|
|
run_system_systemctl enable "${SYSTEM_SERVICE_NAME}"
|
|
run_system_systemctl enable "${LIFECYCLE_SERVICE_NAME}"
|
|
run_system_systemctl restart "${SYSTEM_SERVICE_NAME}"
|
|
run_system_systemctl restart "${LIFECYCLE_SERVICE_NAME}"
|
|
|
|
ensure_user_manager_available
|
|
run_user_systemctl_checked daemon-reload
|
|
import_user_environment
|
|
run_user_systemctl disable zenbook-duo-user.service >/dev/null 2>&1 || true
|
|
run_user_systemctl stop zenbook-duo-user.service >/dev/null 2>&1 || true
|
|
run_user_systemctl_checked enable "${USER_SERVICE_NAME}"
|
|
run_user_systemctl_checked restart "${USER_SERVICE_NAME}"
|
|
|
|
echo "Rust runtime services installed."
|