Files
justinandClaude Opus 5.5 690c9dbebd Add install/uninstall scripts and rewrite the README around them
install.sh sets up a private virtualenv, the udev rule (group picked
automatically), and optionally a systemd user service (--service) and
LAN access with a subnet-limited firewalld/ufw rule (--lan). It records
what it changed so uninstall.sh can undo exactly that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:48:49 -05:00

182 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Install dgm20-linux for the current user.
#
# ./install.sh commands + udev rule (localhost use)
# ./install.sh --service ...and run the web page in the background
# ./install.sh --lan ...and make it reachable from your local network
#
# Run it as yourself, not root; it asks for sudo only for the udev rule and,
# with --lan, the firewall. Undo everything with ./uninstall.sh.
set -euo pipefail
APP=dgm20-linux
SRC=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
DATA=${XDG_DATA_HOME:-$HOME/.local/share}/$APP
BIN=$HOME/.local/bin
UNIT_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user
RULE=/etc/udev/rules.d/70-maono-dgm20.rules
STATE=$DATA/install.state
PORT=8720
SERVICE=0
LAN=0
UDEV=1
GROUP=
DRY=0
usage() {
cat <<EOF
Usage: ./install.sh [options]
--service start the web page now and at login (systemd user service)
--lan listen on all interfaces and open the port to your local
subnet in firewalld or ufw (implies --service). There is no
login on the page: anyone on that subnet can change settings.
--port N web page port (default $PORT)
--group NAME group allowed to use the mic over ssh (default: first of
plugdev, wheel, sudo that you belong to)
--no-udev skip the udev rule
--dry-run show system changes instead of making them
-h, --help show this help
EOF
}
while [ $# -gt 0 ]; do
case $1 in
--service) SERVICE=1 ;;
--lan) LAN=1; SERVICE=1 ;;
--port) PORT=${2:?--port needs a number}; shift ;;
--group) GROUP=${2:?--group needs a name}; shift ;;
--no-udev) UDEV=0 ;;
--dry-run) DRY=1 ;;
-h|--help) usage; exit 0 ;;
*) echo "unknown option: $1" >&2; usage >&2; exit 2 ;;
esac
shift
done
say() { printf '\033[1m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[33mwarning:\033[0m %s\n' "$*" >&2; }
die() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; }
# run a command, or print it under --dry-run
run() { if [ $DRY = 1 ]; then printf ' [dry-run] %s\n' "$*"; else "$@"; fi; }
record() { [ $DRY = 1 ] || echo "$1" >> "$STATE"; }
[ "$(id -u)" != 0 ] || die "run this as your normal user, not root (it uses sudo where needed)"
[ "$(uname -s)" = Linux ] || die "this tool is for Linux"
[[ $PORT =~ ^[0-9]+$ ]] && [ "$PORT" -ge 1 ] && [ "$PORT" -le 65535 ] || die "bad port: $PORT"
command -v python3 >/dev/null || die "python3 not found; install it with your package manager"
python3 -c 'import sys; sys.exit(sys.version_info < (3, 9))' || die "python 3.9 or newer is required"
python3 -c 'import venv, ensurepip' 2>/dev/null \
|| die "python venv support is missing (Debian/Ubuntu: sudo apt install python3-venv)"
# 1. the program, in its own virtualenv
say "Installing $APP into $DATA"
run mkdir -p "$DATA" "$BIN"
[ $DRY = 1 ] || : > "$STATE"
run python3 -m venv --clear "$DATA/venv"
run "$DATA/venv/bin/python" -m pip install --quiet --disable-pip-version-check "$SRC"
for cmd in dgm20ctl dgm20-web; do
run ln -sf "$DATA/venv/bin/$cmd" "$BIN/$cmd"
done
case ":$PATH:" in
*":$BIN:"*) ;;
*) warn "$BIN is not on your PATH; add it in your shell profile" ;;
esac
# 2. udev rule so the mic's control channel is usable without root
if [ $UDEV = 1 ]; then
if [ -z "$GROUP" ]; then
for g in plugdev wheel sudo; do
if id -nG | tr ' ' '\n' | grep -qx "$g"; then GROUP=$g; break; fi
done
fi
if [ -n "$GROUP" ]; then
getent group "$GROUP" >/dev/null || die "group $GROUP does not exist"
line="SUBSYSTEM==\"hidraw\", ATTRS{idVendor}==\"352f\", ATTRS{idProduct}==\"0105\", MODE=\"0660\", GROUP=\"$GROUP\", TAG+=\"uaccess\""
say "Installing udev rule (desktop logins + group '$GROUP'), sudo needed"
else
line='SUBSYSTEM=="hidraw", ATTRS{idVendor}=="352f", ATTRS{idProduct}=="0105", TAG+="uaccess"'
say "Installing udev rule (desktop logins only; use --group for ssh access), sudo needed"
fi
rule="# Maono DGM20 USB microphone: control channel for dgm20-linux
$line"
if [ $DRY = 1 ]; then
printf ' [dry-run] write %s:\n%s\n' "$RULE" "$(sed 's/^/ /' <<<"$rule")"
else
printf '%s\n' "$rule" | sudo tee "$RULE" >/dev/null
fi
run sudo udevadm control --reload
run sudo udevadm trigger --subsystem-match=hidraw
record "udev $RULE"
fi
# 3. background service
HOST=127.0.0.1
[ $LAN = 1 ] && HOST=0.0.0.0
if [ $SERVICE = 1 ]; then
command -v systemctl >/dev/null || die "--service needs systemd"
say "Installing systemd user service on $HOST:$PORT"
unit="[Unit]
Description=Maono DGM20 control page
[Service]
ExecStart=$DATA/venv/bin/dgm20-web --host $HOST --port $PORT
Restart=on-failure
[Install]
WantedBy=default.target"
run mkdir -p "$UNIT_DIR"
if [ $DRY = 1 ]; then
printf ' [dry-run] write %s/dgm20-web.service\n' "$UNIT_DIR"
else
printf '%s\n' "$unit" > "$UNIT_DIR/dgm20-web.service"
fi
run systemctl --user daemon-reload
run systemctl --user enable dgm20-web.service
run systemctl --user restart dgm20-web.service
record "service dgm20-web.service"
if [ "$(loginctl show-user "$USER" -p Linger --value 2>/dev/null)" != yes ]; then
echo " The page runs while you're logged in. To keep it running after you log out:"
echo " sudo loginctl enable-linger $USER"
fi
fi
# 4. firewall, only with --lan, only for the local subnet
if [ $LAN = 1 ]; then
dev=$(ip route get 1.1.1.1 2>/dev/null | sed -n 's/.* dev \([^ ]*\).*/\1/p')
SUBNET=$(ip -o -f inet route show dev "$dev" scope link 2>/dev/null | awk '{print $1; exit}')
[ -n "$SUBNET" ] || die "could not work out your local subnet; open tcp/$PORT yourself"
if command -v firewall-cmd >/dev/null && sudo firewall-cmd --state >/dev/null 2>&1; then
rich="rule family=ipv4 source address=$SUBNET port port=$PORT protocol=tcp accept"
say "Opening tcp/$PORT to $SUBNET in firewalld"
run sudo firewall-cmd --permanent --add-rich-rule="$rich"
run sudo firewall-cmd --reload
record "firewalld $rich"
elif command -v ufw >/dev/null && sudo ufw status 2>/dev/null | grep -q 'Status: active'; then
say "Opening tcp/$PORT to $SUBNET in ufw"
run sudo ufw allow from "$SUBNET" to any port "$PORT" proto tcp comment dgm20-web
record "ufw $SUBNET $PORT"
else
warn "no active firewalld or ufw found. If another firewall is running, allow tcp/$PORT from $SUBNET yourself."
fi
fi
echo
say "Done."
if [ $UDEV = 1 ]; then
echo " Unplug and replug the mic once so the new permissions apply."
fi
if [ $SERVICE = 1 ]; then
if [ $LAN = 1 ]; then
ip=$(ip -o -f inet addr show dev "$dev" | awk '{sub("/.*", "", $4); print $4; exit}')
echo " Control page: http://localhost:$PORT/ (from other devices: http://$ip:$PORT/)"
else
echo " Control page: http://localhost:$PORT/"
fi
else
echo " Start the control page with: dgm20-web --open"
fi
echo " Command line: dgm20ctl --help"