#!/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 <&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"