mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 21:03:15 +02:00
416 lines
10 KiB
Bash
Executable File
416 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/control-from-mobile"
|
|
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/control-from-mobile"
|
|
PASSWORD_FILE="$CONFIG_DIR/vnc.pass"
|
|
ENV_FILE="$CONFIG_DIR/env"
|
|
RUNNER_FILE="$CONFIG_DIR/start-x11vnc.sh"
|
|
SERVICE_NAME="control-from-mobile.service"
|
|
SYSTEMD_USER_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
|
|
SERVICE_FILE="$SYSTEMD_USER_DIR/$SERVICE_NAME"
|
|
DEFAULT_DISPLAY="${DISPLAY:-:0}"
|
|
DEFAULT_PORT=5901
|
|
DEFAULT_BIND_ADDR="0.0.0.0"
|
|
readonly SCRIPT_NAME CONFIG_DIR STATE_DIR PASSWORD_FILE ENV_FILE RUNNER_FILE SERVICE_NAME SYSTEMD_USER_DIR SERVICE_FILE DEFAULT_DISPLAY DEFAULT_PORT DEFAULT_BIND_ADDR
|
|
|
|
usage() {
|
|
cat << 'EOF'
|
|
Usage: control_from_mobile.sh <command> [options]
|
|
|
|
Commands:
|
|
setup [--force-password] Install dependencies, create configs, and write the systemd user service.
|
|
start Start the VNC bridge (via systemd user unit when available).
|
|
stop Stop the bridge.
|
|
restart Restart the bridge.
|
|
status Show whether the bridge service is running.
|
|
enable Enable the service so it starts after login.
|
|
disable Disable automatic start after login.
|
|
info Show connection details and Android app suggestions.
|
|
uninstall Stop the service and remove generated files (keeps password unless --purge).
|
|
help Show this message.
|
|
|
|
Options:
|
|
--force-password Regenerate the VNC password during setup.
|
|
--purge Delete the stored VNC password during uninstall.
|
|
|
|
Examples:
|
|
./control_from_mobile.sh setup
|
|
./control_from_mobile.sh start
|
|
./control_from_mobile.sh info
|
|
|
|
EOF
|
|
}
|
|
|
|
log() {
|
|
printf '[%s] %s\n' "$SCRIPT_NAME" "$*"
|
|
}
|
|
|
|
warn() {
|
|
printf '[%s] %s\n' "$SCRIPT_NAME" "$*" >&2
|
|
}
|
|
|
|
die() {
|
|
warn "$*"
|
|
exit 1
|
|
}
|
|
|
|
require_non_root() {
|
|
if [[ ${EUID:-$(id -u)} -eq 0 ]]; then
|
|
die "Run this script as a regular desktop user, not root."
|
|
fi
|
|
}
|
|
|
|
prompt_yes_no() {
|
|
local prompt="$1"
|
|
local reply
|
|
read -r -p "$prompt [y/N]: " reply
|
|
case "$reply" in
|
|
[Yy][Ee][Ss] | [Yy]) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
ensure_directories() {
|
|
mkdir -p "$CONFIG_DIR" "$STATE_DIR" "$SYSTEMD_USER_DIR"
|
|
chmod 700 "$CONFIG_DIR"
|
|
}
|
|
|
|
missing_commands() {
|
|
local missing=()
|
|
for cmd in "$@"; do
|
|
if ! command -v "$cmd" > /dev/null 2>&1; then
|
|
missing+=("$cmd")
|
|
fi
|
|
done
|
|
printf '%s\n' "${missing[@]-}"
|
|
}
|
|
|
|
install_dependencies() {
|
|
if ! command -v systemctl > /dev/null 2>&1; then
|
|
die "systemctl not found. Install systemd before running this script."
|
|
fi
|
|
|
|
local required=(x11vnc qrencode ssh)
|
|
local needed=()
|
|
mapfile -t needed < <(missing_commands "${required[@]}")
|
|
if ((${#needed[@]} == 0)); then
|
|
log "All required packages (${required[*]}) are present."
|
|
return
|
|
fi
|
|
|
|
if command -v pacman > /dev/null 2>&1; then
|
|
log "Installing missing packages: ${needed[*]}"
|
|
sudo pacman -S --needed --noconfirm "${needed[@]}"
|
|
else
|
|
die "Missing commands (${needed[*]}). Install them manually and rerun setup."
|
|
fi
|
|
}
|
|
|
|
create_password_file() {
|
|
local force=${1:-0}
|
|
if [[ -f $PASSWORD_FILE && $force -ne 1 ]]; then
|
|
log "Using existing VNC password file at $PASSWORD_FILE"
|
|
return
|
|
fi
|
|
|
|
if [[ -f $PASSWORD_FILE ]]; then
|
|
if ! prompt_yes_no "Regenerate the stored VNC password?"; then
|
|
log "Keeping existing password."
|
|
return
|
|
fi
|
|
fi
|
|
|
|
local password confirm generated=0
|
|
read -rsp "Enter VNC password (leave blank to auto-generate): " password
|
|
printf '\n'
|
|
if [[ -z $password ]]; then
|
|
generated=1
|
|
password=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 8)
|
|
log "Generated VNC password: $password"
|
|
else
|
|
read -rsp "Confirm password: " confirm
|
|
printf '\n'
|
|
if [[ $password != "$confirm" ]]; then
|
|
die "Passwords do not match."
|
|
fi
|
|
fi
|
|
|
|
local tmp
|
|
tmp=$(mktemp)
|
|
x11vnc -storepasswd "$password" "$tmp" > /dev/null
|
|
install -m 600 "$tmp" "$PASSWORD_FILE"
|
|
rm -f "$tmp"
|
|
|
|
if ((generated == 0)); then
|
|
log "Password stored securely at $PASSWORD_FILE (hashed)."
|
|
else
|
|
log "Please write down the generated password; it will be needed on your Android device."
|
|
fi
|
|
}
|
|
|
|
create_env_file() {
|
|
if [[ -f $ENV_FILE ]]; then
|
|
return
|
|
fi
|
|
cat > "$ENV_FILE" << EOF
|
|
# control-from-mobile configuration
|
|
# Adjust these values if needed and rerun: systemctl --user restart $SERVICE_NAME
|
|
X11_DISPLAY="$DEFAULT_DISPLAY"
|
|
VNC_PORT="$DEFAULT_PORT"
|
|
# Use 127.0.0.1 to force SSH tunnel-only access, or 0.0.0.0 to expose on LAN.
|
|
VNC_BIND_ADDR="$DEFAULT_BIND_ADDR"
|
|
EOF
|
|
chmod 600 "$ENV_FILE"
|
|
}
|
|
|
|
create_runner_script() {
|
|
cat > "$RUNNER_FILE" << 'EOF'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
CONFIG_DIR="$(dirname "$(readlink -f "$0")")"
|
|
PASSWORD_FILE="$CONFIG_DIR/vnc.pass"
|
|
ENV_FILE="$CONFIG_DIR/env"
|
|
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/control-from-mobile"
|
|
mkdir -p "$STATE_DIR"
|
|
|
|
if [[ ! -f "$PASSWORD_FILE" ]]; then
|
|
echo "Missing VNC password file at $PASSWORD_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$ENV_FILE"
|
|
fi
|
|
|
|
X11_DISPLAY="${X11_DISPLAY:-${DISPLAY:-:0}}"
|
|
VNC_PORT="${VNC_PORT:-5901}"
|
|
VNC_BIND_ADDR="${VNC_BIND_ADDR:-0.0.0.0}"
|
|
|
|
LOG_FILE="$STATE_DIR/x11vnc.log"
|
|
exec /usr/bin/x11vnc \
|
|
-display "$X11_DISPLAY" \
|
|
-rfbport "$VNC_PORT" \
|
|
-listen "$VNC_BIND_ADDR" \
|
|
-forever \
|
|
-shared \
|
|
-auth guess \
|
|
-rfbauth "$PASSWORD_FILE" \
|
|
-noxdamage \
|
|
-repeat \
|
|
-ncache 10 \
|
|
-ncache_cr \
|
|
-o "$LOG_FILE"
|
|
EOF
|
|
chmod 700 "$RUNNER_FILE"
|
|
}
|
|
|
|
create_service_file() {
|
|
cat > "$SERVICE_FILE" << EOF
|
|
[Unit]
|
|
Description=Expose X11 desktop over VNC for Android control
|
|
After=graphical-session.target
|
|
PartOf=graphical-session.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
EnvironmentFile=$ENV_FILE
|
|
ExecStart=$RUNNER_FILE
|
|
Restart=on-failure
|
|
RestartSec=2
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
EOF
|
|
}
|
|
|
|
reload_user_daemon() {
|
|
systemctl --user daemon-reload
|
|
}
|
|
|
|
ensure_service_present() {
|
|
if [[ ! -f $SERVICE_FILE || ! -x $RUNNER_FILE ]]; then
|
|
die "Service files missing. Run: $SCRIPT_NAME setup"
|
|
fi
|
|
}
|
|
|
|
start_service() {
|
|
ensure_service_present
|
|
systemctl --user start "$SERVICE_NAME"
|
|
}
|
|
|
|
stop_service() {
|
|
systemctl --user stop "$SERVICE_NAME" || true
|
|
}
|
|
|
|
status_service() {
|
|
if systemctl --user is-active --quiet "$SERVICE_NAME"; then
|
|
log "Service is active."
|
|
else
|
|
log "Service is inactive."
|
|
fi
|
|
systemctl --user status "$SERVICE_NAME" --no-pager || true
|
|
}
|
|
|
|
enable_service() {
|
|
ensure_service_present
|
|
systemctl --user enable "$SERVICE_NAME"
|
|
}
|
|
|
|
disable_service() {
|
|
systemctl --user disable "$SERVICE_NAME" || true
|
|
}
|
|
|
|
show_info() {
|
|
ensure_service_present
|
|
# shellcheck disable=SC1090
|
|
[[ -f $ENV_FILE ]] && source "$ENV_FILE"
|
|
local port="${VNC_PORT:-$DEFAULT_PORT}"
|
|
local bind_addr="${VNC_BIND_ADDR:-$DEFAULT_BIND_ADDR}"
|
|
local display="${X11_DISPLAY:-$DEFAULT_DISPLAY}"
|
|
|
|
local is_active="inactive"
|
|
if systemctl --user is-active --quiet "$SERVICE_NAME"; then
|
|
is_active="active"
|
|
fi
|
|
|
|
log "Service status: $is_active"
|
|
log "Display: $display"
|
|
log "Listening address: $bind_addr"
|
|
log "VNC port: $port"
|
|
log "Password file: $PASSWORD_FILE"
|
|
|
|
local -a ip_list=()
|
|
if command -v hostname > /dev/null 2>&1; then
|
|
while IFS= read -r line; do
|
|
[[ -z $line ]] && continue
|
|
ip_list+=("$line")
|
|
done < <(hostname -I 2> /dev/null | tr ' ' '\n' | grep -E '^[0-9]' || true)
|
|
fi
|
|
|
|
if ((${#ip_list[@]} > 0)); then
|
|
log "Detected LAN IPs:"
|
|
for ip in "${ip_list[@]}"; do
|
|
printf ' - %s\n' "$ip"
|
|
done
|
|
else
|
|
warn "Could not detect LAN IPs."
|
|
fi
|
|
|
|
printf '\nRecommended Android clients (FOSS):\n'
|
|
printf ' • bVNC (available on F-Droid) — supports full control.\n'
|
|
printf ' • Termux + OpenSSH for establishing an SSH tunnel when exposing only on 127.0.0.1.\n'
|
|
printf '\nConnect via VNC:\n'
|
|
printf ' Host: <your-ip>\n Port: %s\n Password: <stored during setup>\n' "$port"
|
|
|
|
local qr_host
|
|
if ((${#ip_list[@]} > 0)); then
|
|
qr_host="${ip_list[0]}"
|
|
else
|
|
qr_host="$bind_addr"
|
|
if [[ $qr_host == "0.0.0.0" || $qr_host == "::" ]]; then
|
|
qr_host="127.0.0.1"
|
|
fi
|
|
warn "Using fallback host $qr_host for QR code; replace with an accessible IP if needed."
|
|
fi
|
|
|
|
if command -v qrencode > /dev/null 2>&1; then
|
|
printf '\nConnection QR (vnc://%s:%s):\n' "$qr_host" "$port"
|
|
qrencode -o - "vnc://$qr_host:$port" -t ASCII || true
|
|
else
|
|
warn "qrencode not found; reinstall qrencode to get QR codes."
|
|
fi
|
|
|
|
printf '\nFor encrypted access outside your LAN, use Termux on Android:\n'
|
|
printf ' ssh -L %s:localhost:%s <user>@<public-ip>\n' "$port" "$port"
|
|
printf 'Then point bVNC to 127.0.0.1:%s.\n' "$port"
|
|
}
|
|
|
|
uninstall_files() {
|
|
local purge_password=${1:-0}
|
|
stop_service
|
|
disable_service
|
|
rm -f "$SERVICE_FILE"
|
|
rm -f "$RUNNER_FILE"
|
|
rm -f "$ENV_FILE"
|
|
if ((purge_password)); then
|
|
rm -f "$PASSWORD_FILE"
|
|
log "Removed password file."
|
|
fi
|
|
reload_user_daemon
|
|
log "Removed generated files."
|
|
}
|
|
|
|
main() {
|
|
require_non_root
|
|
|
|
local cmd="${1:-}"
|
|
shift || true
|
|
|
|
case "$cmd" in
|
|
setup)
|
|
local force=0
|
|
if [[ ${1:-} == "--force-password" ]]; then
|
|
force=1
|
|
shift || true
|
|
fi
|
|
ensure_directories
|
|
install_dependencies
|
|
create_password_file "$force"
|
|
create_env_file
|
|
create_runner_script
|
|
create_service_file
|
|
reload_user_daemon
|
|
log "Setup complete. Start the service with: $SCRIPT_NAME start"
|
|
;;
|
|
start)
|
|
start_service
|
|
show_info
|
|
;;
|
|
stop)
|
|
stop_service
|
|
;;
|
|
restart)
|
|
stop_service
|
|
start_service
|
|
;;
|
|
status)
|
|
status_service
|
|
;;
|
|
enable)
|
|
enable_service
|
|
;;
|
|
disable)
|
|
disable_service
|
|
;;
|
|
info)
|
|
show_info
|
|
;;
|
|
uninstall)
|
|
local purge=0
|
|
if [[ ${1:-} == "--purge" ]]; then
|
|
purge=1
|
|
shift || true
|
|
fi
|
|
uninstall_files "$purge"
|
|
;;
|
|
help | --help | -h | "")
|
|
usage
|
|
;;
|
|
*)
|
|
usage
|
|
die "Unknown command: $cmd"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|