Add files via upload
This commit is contained in:
@@ -0,0 +1,704 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Interactive Kiosk Mumble Client Installer
|
||||
# With systemd service and fixed audio
|
||||
|
||||
set -e
|
||||
|
||||
echo "========================================"
|
||||
echo "Kiosk Mumble Client Installer"
|
||||
echo "Interactive Setup with Systemd Service"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
print_success() { echo -e "${GREEN}✓${NC} $1"; }
|
||||
print_error() { echo -e "${RED}✗${NC} $1"; }
|
||||
print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
||||
print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
|
||||
|
||||
TARGET_USER="${1:-kiosk}"
|
||||
USER_HOME=$(eval echo "~$TARGET_USER")
|
||||
|
||||
echo ""
|
||||
print_info "Target user: $TARGET_USER"
|
||||
print_info "Home directory: $USER_HOME"
|
||||
echo ""
|
||||
|
||||
# Check if user exists
|
||||
if ! id "$TARGET_USER" &>/dev/null; then
|
||||
print_error "User $TARGET_USER does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Interactive configuration
|
||||
echo "========================================="
|
||||
echo "Mumble Server Configuration"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
read -p "Mumble server address [mumble.example.org]: " MUMBLE_SERVER
|
||||
MUMBLE_SERVER=${MUMBLE_SERVER:-mumble.example.org}
|
||||
|
||||
read -p "Port [64738]: " MUMBLE_PORT
|
||||
MUMBLE_PORT=${MUMBLE_PORT:-64738}
|
||||
|
||||
read -p "Username [$TARGET_USER]: " MUMBLE_USERNAME
|
||||
MUMBLE_USERNAME=${MUMBLE_USERNAME:-$TARGET_USER}
|
||||
|
||||
read -sp "Password (leave empty if none): " MUMBLE_PASSWORD
|
||||
echo ""
|
||||
|
||||
read -p "Channel to join (leave empty for root): " MUMBLE_CHANNEL
|
||||
|
||||
read -p "Skip SSL verification? (y/N): " MUMBLE_INSECURE
|
||||
MUMBLE_INSECURE=${MUMBLE_INSECURE:-n}
|
||||
|
||||
echo ""
|
||||
echo "Configuration Summary:"
|
||||
echo "----------------------"
|
||||
echo "Server: $MUMBLE_SERVER:$MUMBLE_PORT"
|
||||
echo "Username: $MUMBLE_USERNAME"
|
||||
echo "Password: $([ -n "$MUMBLE_PASSWORD" ] && echo '(set)' || echo '(none)')"
|
||||
echo "Channel: ${MUMBLE_CHANNEL:-(root)}"
|
||||
echo "Skip SSL: $MUMBLE_INSECURE"
|
||||
echo ""
|
||||
read -p "Continue with installation? (Y/n): " CONFIRM
|
||||
if [[ "$CONFIRM" =~ ^[Nn]$ ]]; then
|
||||
echo "Installation cancelled"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check prerequisites
|
||||
print_info "Checking prerequisites..."
|
||||
|
||||
if ! command -v go &> /dev/null; then
|
||||
print_warning "Go not installed, installing..."
|
||||
sudo apt update
|
||||
sudo apt install -y golang-go
|
||||
fi
|
||||
print_success "Go installed: $(go version)"
|
||||
|
||||
# Install system dependencies
|
||||
print_info "Installing system dependencies..."
|
||||
sudo apt install -y \
|
||||
libopus-dev \
|
||||
portaudio19-dev \
|
||||
pkg-config \
|
||||
build-essential \
|
||||
git \
|
||||
pulseaudio-utils
|
||||
|
||||
print_success "System dependencies installed"
|
||||
|
||||
# Create workspace
|
||||
WORK_DIR="$USER_HOME/kiosk-mumble"
|
||||
print_info "Creating workspace at $WORK_DIR..."
|
||||
sudo -u "$TARGET_USER" mkdir -p "$WORK_DIR"
|
||||
|
||||
# Save the Go source code with FIXED audio callback
|
||||
print_info "Creating source files..."
|
||||
sudo -u "$TARGET_USER" bash << 'EOF'
|
||||
cat > "$HOME/kiosk-mumble/main.go" << 'GOSRC'
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gordonklaus/portaudio"
|
||||
"layeh.com/gumble/gumble"
|
||||
"layeh.com/gumble/gumbleutil"
|
||||
_ "layeh.com/gumble/opus"
|
||||
)
|
||||
|
||||
const (
|
||||
AudioSampleRate = 48000
|
||||
AudioFrameSize = 960
|
||||
AudioChannels = 1
|
||||
)
|
||||
|
||||
type MumbleClient struct {
|
||||
client *gumble.Client
|
||||
config *gumble.Config
|
||||
stream *portaudio.Stream
|
||||
transmitting bool
|
||||
targetChannel string
|
||||
audioOut chan<- gumble.AudioBuffer
|
||||
pttDevice string
|
||||
pttKeyCode int
|
||||
}
|
||||
|
||||
func main() {
|
||||
server := flag.String("server", "localhost:64738", "Mumble server (host:port)")
|
||||
username := flag.String("username", "kiosk-client", "Username")
|
||||
password := flag.String("password", "", "Server password")
|
||||
channel := flag.String("channel", "", "Channel to join")
|
||||
insecure := flag.Bool("insecure", false, "Skip TLS verification")
|
||||
vad := flag.Bool("vad", false, "Voice activity detection")
|
||||
pttKey := flag.String("ptt-key", "", "PTT key: 'space', 'mute', 'f13', etc")
|
||||
pttDevice := flag.String("ptt-device", "", "PTT input device")
|
||||
listDevices := flag.Bool("list-devices", false, "List input devices and exit")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *listDevices {
|
||||
listInputDevices()
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("╔════════════════════════════════╗")
|
||||
fmt.Println("║ Kiosk Mumble Client v1.0 ║")
|
||||
fmt.Println("╚════════════════════════════════╝")
|
||||
fmt.Println()
|
||||
|
||||
if err := portaudio.Initialize(); err != nil {
|
||||
log.Fatalf("PortAudio init failed: %v", err)
|
||||
}
|
||||
defer portaudio.Terminate()
|
||||
|
||||
config := gumble.NewConfig()
|
||||
config.Username = *username
|
||||
config.Password = *password
|
||||
|
||||
tlsConfig := &tls.Config{InsecureSkipVerify: *insecure}
|
||||
|
||||
mc := &MumbleClient{
|
||||
config: config,
|
||||
transmitting: *vad,
|
||||
targetChannel: *channel,
|
||||
pttDevice: *pttDevice,
|
||||
}
|
||||
|
||||
if *pttKey != "" {
|
||||
mc.pttKeyCode = parsePTTKey(*pttKey)
|
||||
if mc.pttDevice == "" {
|
||||
mc.pttDevice = findBestInputDevice()
|
||||
}
|
||||
}
|
||||
|
||||
config.Attach(gumbleutil.Listener{
|
||||
Connect: mc.onConnect,
|
||||
Disconnect: mc.onDisconnect,
|
||||
UserChange: mc.onUserChange,
|
||||
TextMessage: mc.onTextMessage,
|
||||
})
|
||||
|
||||
fmt.Printf("Connecting to %s as '%s'...\n", *server, *username)
|
||||
if *password != "" {
|
||||
fmt.Println("Using password authentication")
|
||||
}
|
||||
|
||||
client, err := gumble.DialWithDialer(new(net.Dialer), *server, config, tlsConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("Connection failed: %v", err)
|
||||
}
|
||||
mc.client = client
|
||||
defer client.Disconnect()
|
||||
|
||||
fmt.Println("✓ Connected!")
|
||||
|
||||
if err := mc.setupAudio(); err != nil {
|
||||
log.Fatalf("Audio setup failed: %v", err)
|
||||
}
|
||||
defer mc.stream.Close()
|
||||
|
||||
if err := mc.stream.Start(); err != nil {
|
||||
log.Fatalf("Audio start failed: %v", err)
|
||||
}
|
||||
defer mc.stream.Stop()
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Controls:")
|
||||
if *vad {
|
||||
fmt.Println(" Mode: Voice Activity Detection (always transmitting)")
|
||||
} else if *pttKey != "" {
|
||||
fmt.Printf(" Mode: Push-to-Talk (key: %s)\n", *pttKey)
|
||||
if mc.pttDevice != "" {
|
||||
fmt.Printf(" Device: %s\n", mc.pttDevice)
|
||||
}
|
||||
} else {
|
||||
fmt.Println(" Mode: Manual (no transmission)")
|
||||
}
|
||||
fmt.Println(" Ctrl+C: Quit")
|
||||
fmt.Println()
|
||||
|
||||
if *pttKey != "" && mc.pttDevice != "" {
|
||||
go mc.monitorPTT()
|
||||
}
|
||||
|
||||
if mc.targetChannel != "" {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
mc.joinChannel(mc.targetChannel)
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
|
||||
fmt.Println("\nDisconnecting...")
|
||||
}
|
||||
|
||||
func (mc *MumbleClient) setupAudio() error {
|
||||
mc.audioOut = mc.client.AudioOutgoing()
|
||||
|
||||
h, err := portaudio.DefaultHostApi()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get default host API: %w", err)
|
||||
}
|
||||
|
||||
p := portaudio.LowLatencyParameters(h.DefaultInputDevice, nil)
|
||||
p.Input.Channels = AudioChannels
|
||||
|
||||
fmt.Printf("Audio: Using device '%s'\n", h.DefaultInputDevice.Name)
|
||||
|
||||
stream, err := portaudio.OpenStream(p, func(in, out []int16) {
|
||||
if !mc.transmitting || mc.audioOut == nil {
|
||||
return
|
||||
}
|
||||
pcm := make([]int16, len(in))
|
||||
copy(pcm, in)
|
||||
select {
|
||||
case mc.audioOut <- pcm:
|
||||
default:
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open audio stream: %w", err)
|
||||
}
|
||||
|
||||
mc.stream = stream
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc *MumbleClient) monitorPTT() {
|
||||
file, err := os.Open(mc.pttDevice)
|
||||
if err != nil {
|
||||
fmt.Printf("⚠ Failed to open PTT device %s: %v\n", mc.pttDevice, err)
|
||||
fmt.Println("⚠ PTT will not work. Try running with sudo or add user to 'input' group:")
|
||||
fmt.Printf(" sudo usermod -a -G input %s\n", os.Getenv("USER"))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fmt.Printf("✓ PTT monitoring started on %s\n", mc.pttDevice)
|
||||
|
||||
type inputEvent struct {
|
||||
Time syscall.Timeval
|
||||
Type uint16
|
||||
Code uint16
|
||||
Value int32
|
||||
}
|
||||
|
||||
for {
|
||||
var event inputEvent
|
||||
err := binary.Read(file, binary.LittleEndian, &event)
|
||||
if err != nil {
|
||||
fmt.Printf("⚠ PTT monitoring error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if event.Type == 1 && int(event.Code) == mc.pttKeyCode {
|
||||
if event.Value == 1 {
|
||||
mc.transmitting = true
|
||||
fmt.Println("🎤 Transmitting...")
|
||||
} else if event.Value == 0 {
|
||||
mc.transmitting = false
|
||||
fmt.Println("🔇 Not transmitting")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parsePTTKey(key string) int {
|
||||
keyMap := map[string]int{
|
||||
"space": 57,
|
||||
"mute": 113,
|
||||
"volumeup": 115,
|
||||
"volumedown": 114,
|
||||
"playpause": 164,
|
||||
"previoussong": 165,
|
||||
"nextsong": 163,
|
||||
"f13": 183,
|
||||
"f14": 184,
|
||||
"f15": 185,
|
||||
"f16": 186,
|
||||
"f17": 187,
|
||||
"f18": 188,
|
||||
"f19": 189,
|
||||
"f20": 190,
|
||||
"leftctrl": 29,
|
||||
"rightctrl": 97,
|
||||
"leftalt": 56,
|
||||
"rightalt": 100,
|
||||
"capslock": 58,
|
||||
"numlock": 69,
|
||||
"scrolllock": 70,
|
||||
}
|
||||
|
||||
key = strings.ToLower(strings.TrimSpace(key))
|
||||
if code, ok := keyMap[key]; ok {
|
||||
return code
|
||||
}
|
||||
|
||||
var code int
|
||||
if _, err := fmt.Sscanf(key, "%d", &code); err == nil {
|
||||
return code
|
||||
}
|
||||
|
||||
fmt.Printf("⚠ Unknown key '%s', using Space\n", key)
|
||||
return 57
|
||||
}
|
||||
|
||||
func findBestInputDevice() string {
|
||||
devices, err := filepath.Glob("/dev/input/event*")
|
||||
if err != nil || len(devices) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, dev := range devices {
|
||||
name := getDeviceName(dev)
|
||||
if strings.Contains(strings.ToLower(name), "keyboard") {
|
||||
return dev
|
||||
}
|
||||
}
|
||||
|
||||
if len(devices) > 0 {
|
||||
return devices[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getDeviceName(devicePath string) string {
|
||||
sysPath := "/sys/class/input/" + filepath.Base(devicePath) + "/device/name"
|
||||
data, err := os.ReadFile(sysPath)
|
||||
if err == nil {
|
||||
return strings.TrimSpace(string(data))
|
||||
}
|
||||
return devicePath
|
||||
}
|
||||
|
||||
func listInputDevices() {
|
||||
fmt.Println("Available Input Devices:")
|
||||
fmt.Println("========================")
|
||||
fmt.Println()
|
||||
|
||||
devices, err := filepath.Glob("/dev/input/event*")
|
||||
if err != nil {
|
||||
fmt.Println("Error listing devices:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(devices) == 0 {
|
||||
fmt.Println("No input devices found")
|
||||
fmt.Println("Note: You may need to run with sudo")
|
||||
return
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`event(\d+)`)
|
||||
for _, dev := range devices {
|
||||
name := getDeviceName(dev)
|
||||
match := re.FindStringSubmatch(dev)
|
||||
eventNum := ""
|
||||
if len(match) > 1 {
|
||||
eventNum = match[1]
|
||||
}
|
||||
|
||||
fmt.Printf(" %s\n", dev)
|
||||
fmt.Printf(" Name: %s\n", name)
|
||||
if eventNum != "" {
|
||||
fmt.Printf(" Event: %s\n", eventNum)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
fmt.Println("Common PTT Keys:")
|
||||
fmt.Println(" -ptt-key mute # Mute media key")
|
||||
fmt.Println(" -ptt-key space # Spacebar")
|
||||
fmt.Println(" -ptt-key f13 # F13")
|
||||
fmt.Println(" -ptt-key capslock # Caps Lock")
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func (mc *MumbleClient) onConnect(e *gumble.ConnectEvent) {
|
||||
fmt.Println("✓ Connected to server")
|
||||
fmt.Printf(" Server: %s\n", e.Client.Conn.RemoteAddr())
|
||||
fmt.Printf(" Users: %d online\n", len(e.Client.Users))
|
||||
fmt.Printf(" Channels: %d\n", len(e.Client.Channels))
|
||||
}
|
||||
|
||||
func (mc *MumbleClient) onDisconnect(e *gumble.DisconnectEvent) {
|
||||
fmt.Println("✗ Disconnected from server")
|
||||
if e.String != "" {
|
||||
fmt.Printf(" Reason: %s\n", e.String)
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *MumbleClient) onUserChange(e *gumble.UserChangeEvent) {
|
||||
if e.Type.Has(gumble.UserChangeConnected) {
|
||||
fmt.Printf("→ %s joined\n", e.User.Name)
|
||||
}
|
||||
if e.Type.Has(gumble.UserChangeDisconnected) {
|
||||
fmt.Printf("← %s left\n", e.User.Name)
|
||||
}
|
||||
if e.Type.Has(gumble.UserChangeChannel) {
|
||||
if e.User.Channel != nil {
|
||||
fmt.Printf("↔ %s moved to %s\n", e.User.Name, e.User.Channel.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *MumbleClient) onTextMessage(e *gumble.TextMessageEvent) {
|
||||
if e.Sender != nil {
|
||||
fmt.Printf("💬 %s: %s\n", e.Sender.Name, e.Message)
|
||||
} else {
|
||||
fmt.Printf("💬 Server: %s\n", e.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *MumbleClient) joinChannel(channelPath string) {
|
||||
if mc.client == nil || mc.client.Self == nil {
|
||||
fmt.Printf("⚠ Client not ready yet, retrying...\n")
|
||||
time.Sleep(1 * time.Second)
|
||||
if mc.client == nil || mc.client.Self == nil {
|
||||
fmt.Printf("⚠ Cannot join channel: client not initialized\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
parts := strings.Split(channelPath, "/")
|
||||
|
||||
var targetChannel *gumble.Channel
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, ch := range mc.client.Channels {
|
||||
if ch.Name == part {
|
||||
if targetChannel == nil {
|
||||
if ch.Parent == nil || ch.Parent.Parent == nil {
|
||||
targetChannel = ch
|
||||
found = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if ch.Parent != nil && ch.Parent.ID == targetChannel.ID {
|
||||
targetChannel = ch
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
fmt.Printf("⚠ Channel not found: %s\n", part)
|
||||
fmt.Println("Available channels:")
|
||||
mc.listChannels()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if targetChannel != nil {
|
||||
mc.client.Self.Move(targetChannel)
|
||||
fmt.Printf("✓ Joined channel: %s\n", channelPath)
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *MumbleClient) listChannels() {
|
||||
for _, ch := range mc.client.Channels {
|
||||
indent := strings.Repeat(" ", getChannelDepth(ch))
|
||||
fmt.Printf("%s- %s\n", indent, ch.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func getChannelDepth(ch *gumble.Channel) int {
|
||||
depth := 0
|
||||
current := ch
|
||||
for current.Parent != nil {
|
||||
depth++
|
||||
current = current.Parent
|
||||
}
|
||||
return depth
|
||||
}
|
||||
GOSRC
|
||||
EOF
|
||||
|
||||
print_success "Source code created"
|
||||
|
||||
# Build
|
||||
print_info "Building client (this may take ~45 minutes)..."
|
||||
sudo -u "$TARGET_USER" bash << 'EOF'
|
||||
cd "$HOME/kiosk-mumble"
|
||||
|
||||
go mod init kiosk-mumble 2>/dev/null || true
|
||||
|
||||
echo "Downloading dependencies..."
|
||||
go get layeh.com/gumble/gumble@latest
|
||||
go get layeh.com/gumble/gumbleutil@latest
|
||||
go get layeh.com/gumble/opus@latest
|
||||
go get github.com/gordonklaus/portaudio@latest
|
||||
|
||||
go mod tidy
|
||||
|
||||
echo "Compiling..."
|
||||
CGO_ENABLED=1 go build -o kiosk-mumble main.go
|
||||
|
||||
if [ -f kiosk-mumble ]; then
|
||||
echo "✓ Build successful!"
|
||||
mkdir -p "$HOME/bin"
|
||||
cp kiosk-mumble "$HOME/bin/"
|
||||
chmod +x "$HOME/bin/kiosk-mumble"
|
||||
echo "✓ Installed to $HOME/bin/kiosk-mumble"
|
||||
else
|
||||
echo "✗ Build failed"
|
||||
exit 1
|
||||
fi
|
||||
EOF
|
||||
|
||||
BUILD_RESULT=$?
|
||||
|
||||
if [ $BUILD_RESULT -ne 0 ]; then
|
||||
print_error "Build failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Build complete!"
|
||||
|
||||
# Configure permissions
|
||||
print_info "Configuring permissions..."
|
||||
|
||||
if ! groups "$TARGET_USER" | grep -q '\binput\b'; then
|
||||
sudo usermod -a -G input "$TARGET_USER"
|
||||
print_success "Added to input group"
|
||||
else
|
||||
print_success "Already in input group"
|
||||
fi
|
||||
|
||||
if ! groups "$TARGET_USER" | grep -q '\baudio\b'; then
|
||||
sudo usermod -a -G audio "$TARGET_USER"
|
||||
print_success "Added to audio group"
|
||||
else
|
||||
print_success "Already in audio group"
|
||||
fi
|
||||
|
||||
# Create systemd service
|
||||
print_info "Creating systemd service..."
|
||||
|
||||
INSECURE_FLAG=""
|
||||
if [[ "$MUMBLE_INSECURE" =~ ^[Yy]$ ]]; then
|
||||
INSECURE_FLAG="-insecure"
|
||||
fi
|
||||
|
||||
PASSWORD_FLAG=""
|
||||
if [ -n "$MUMBLE_PASSWORD" ]; then
|
||||
PASSWORD_FLAG="-password \"$MUMBLE_PASSWORD\""
|
||||
fi
|
||||
|
||||
CHANNEL_FLAG=""
|
||||
if [ -n "$MUMBLE_CHANNEL" ]; then
|
||||
CHANNEL_FLAG="-channel \"$MUMBLE_CHANNEL\""
|
||||
fi
|
||||
|
||||
sudo tee /etc/systemd/system/kiosk-mumble.service > /dev/null << SERVICEEOF
|
||||
[Unit]
|
||||
Description=Kiosk Mumble Client
|
||||
After=network-online.target sound.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$TARGET_USER
|
||||
Group=$TARGET_USER
|
||||
Environment="XDG_RUNTIME_DIR=/run/user/$(id -u $TARGET_USER)"
|
||||
Environment="PULSE_SERVER=/run/user/$(id -u $TARGET_USER)/pulse/native"
|
||||
ExecStart=$USER_HOME/bin/kiosk-mumble -server $MUMBLE_SERVER:$MUMBLE_PORT -username $MUMBLE_USERNAME $PASSWORD_FLAG $CHANNEL_FLAG $INSECURE_FLAG -vad
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICEEOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
print_success "Systemd service created"
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "Installation Complete!"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
print_success "Kiosk Mumble Client installed"
|
||||
echo ""
|
||||
echo "Service Management:"
|
||||
echo "-------------------"
|
||||
echo "Start service: sudo systemctl start kiosk-mumble"
|
||||
echo "Stop service: sudo systemctl stop kiosk-mumble"
|
||||
echo "Enable on boot: sudo systemctl enable kiosk-mumble"
|
||||
echo "View logs: sudo journalctl -u kiosk-mumble -f"
|
||||
echo "Service status: sudo systemctl status kiosk-mumble"
|
||||
echo ""
|
||||
echo "Manual Testing:"
|
||||
echo "---------------"
|
||||
echo "Test audio:"
|
||||
if [ -n "$MUMBLE_CHANNEL" ]; then
|
||||
echo " sudo -u $TARGET_USER $USER_HOME/bin/kiosk-mumble \\"
|
||||
echo " -server $MUMBLE_SERVER:$MUMBLE_PORT \\"
|
||||
echo " -username $MUMBLE_USERNAME \\"
|
||||
echo " -channel \"$MUMBLE_CHANNEL\" \\"
|
||||
echo " -vad"
|
||||
else
|
||||
echo " sudo -u $TARGET_USER $USER_HOME/bin/kiosk-mumble \\"
|
||||
echo " -server $MUMBLE_SERVER:$MUMBLE_PORT \\"
|
||||
echo " -username $MUMBLE_USERNAME \\"
|
||||
echo " -vad"
|
||||
fi
|
||||
echo ""
|
||||
echo "Audio Troubleshooting:"
|
||||
echo "----------------------"
|
||||
echo "Check audio devices:"
|
||||
echo " pactl list short sinks"
|
||||
echo " pactl list short sources"
|
||||
echo ""
|
||||
echo "Test microphone:"
|
||||
echo " arecord -d 5 test.wav && aplay test.wav"
|
||||
echo ""
|
||||
print_warning "Note: User must log out and back in for group changes to take effect!"
|
||||
echo ""
|
||||
|
||||
read -p "Enable and start service now? (Y/n): " START_NOW
|
||||
if [[ ! "$START_NOW" =~ ^[Nn]$ ]]; then
|
||||
sudo systemctl enable kiosk-mumble
|
||||
sudo systemctl start kiosk-mumble
|
||||
print_success "Service enabled and started"
|
||||
echo ""
|
||||
echo "Checking status..."
|
||||
sleep 2
|
||||
sudo systemctl status kiosk-mumble --no-pager
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_info "Installation complete! Check logs with: sudo journalctl -u kiosk-mumble -f"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user