Completely replaced the install_talkkonnect_with_config() function in setup_intercom.sh with the proven method from talkkonnect_complete_install.sh. Key improvements: - Uses working Opus patch for x86_64 architecture - Installs to /usr/local/bin/talkkonnect (not ~/go/bin) - Config in ~/.config/talkkonnect/ (proper XDG location) - Sets <insecure>true</insecure> by default (handles self-signed certs) - Proper XDG_RUNTIME_DIR in systemd service for PipeWire/audio - Automatically enables service (starts on boot) - Better progress feedback and error handling - Uses Go 1.24.1 (latest stable) The function now: 1. Installs all dependencies correctly 2. Builds with proper Opus library integration 3. Creates config with user's server/credentials from prompts 4. Sets up systemd service properly 5. Enables and starts service automatically This makes setup_intercom.sh a complete, reliable one-stop solution that uses the same proven method as talkkonnect_complete_install.sh while maintaining the interactive prompt workflow. Included files: - setup_intercom.sh - Updated with new function - setup_intercom.sh.backup - Backup of original - setup_intercom_talkkonnect_function.txt - Reference for the new function - update_setup_intercom.sh - Script used to perform the replacement
493 lines
14 KiB
Plaintext
493 lines
14 KiB
Plaintext
# This is the replacement install_talkkonnect_with_config() function
|
|
# Copy this into setup_intercom.sh starting at line 197
|
|
|
|
install_talkkonnect_with_config() {
|
|
local server_addr="$1"
|
|
local server_port="$2"
|
|
local tk_user="$3"
|
|
local tk_pass="$4"
|
|
local tk_channel="$5"
|
|
|
|
echo
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " INSTALLING TALKKONNECT CLIENT"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo
|
|
echo "Using proven installation method (same as talkkonnect_complete_install.sh)..."
|
|
echo
|
|
|
|
# Get target user info
|
|
local TARGET_USER="$KIOSK_USER"
|
|
local TARGET_UID=$(id -u "$TARGET_USER")
|
|
local TARGET_HOME="/home/$TARGET_USER"
|
|
local CONFIG_DIR="$TARGET_HOME/.config/talkkonnect"
|
|
|
|
# Verify home directory exists
|
|
if [ ! -d "$TARGET_HOME" ]; then
|
|
log_error "Home directory does not exist: $TARGET_HOME"
|
|
pause
|
|
return 1
|
|
fi
|
|
|
|
# --- System Prep ------------------------------------------------------
|
|
echo "[1/7] Installing system dependencies..."
|
|
sudo apt update
|
|
sudo apt install -y wget git build-essential pkg-config \
|
|
libasound2-dev libopus-dev libopus0 libopusfile-dev \
|
|
libpipewire-0.3-dev libevdev-dev libopenal-dev alsa-utils
|
|
|
|
# --- Go Installation --------------------------------------------------
|
|
echo "[2/7] Installing Go 1.24.1..."
|
|
if ! command -v go &>/dev/null || ! go version | grep -q "go1.24"; then
|
|
sudo rm -rf /usr/local/go
|
|
wget -q https://go.dev/dl/go1.24.1.linux-amd64.tar.gz
|
|
sudo tar -C /usr/local -xzf go1.24.1.linux-amd64.tar.gz
|
|
rm -f go1.24.1.linux-amd64.tar.gz
|
|
sudo ln -sf /usr/local/go/bin/go /usr/bin/go
|
|
export PATH="/usr/local/go/bin:$PATH"
|
|
log_success "Go 1.24.1 installed"
|
|
else
|
|
log_success "Go already installed"
|
|
fi
|
|
|
|
# --- Clone TalkKonnect ------------------------------------------------
|
|
echo "[3/7] Cloning talkkonnect repository..."
|
|
cd ~
|
|
if [ -d "talkkonnect" ]; then
|
|
rm -rf talkkonnect
|
|
fi
|
|
git clone https://github.com/talkkonnect/talkkonnect.git
|
|
cd talkkonnect
|
|
|
|
# --- Patch gopus for x86_64 -------------------------------------------
|
|
echo "[4/7] Applying x86_64 Opus patch..."
|
|
go mod vendor
|
|
|
|
if [ -d "vendor/github.com/talkkonnect/gopus" ]; then
|
|
cat > vendor/github.com/talkkonnect/gopus/opus_nonshared.go << 'EOFOPUS'
|
|
// +build amd64,cgo 386,cgo
|
|
|
|
package gopus
|
|
|
|
// #cgo pkg-config: opus
|
|
// #cgo LDFLAGS: -lm
|
|
//
|
|
// #include <stdio.h>
|
|
// #include <stdlib.h>
|
|
// #include <opus.h>
|
|
//
|
|
// enum {
|
|
// gopus_ok = OPUS_OK,
|
|
// gopus_bad_arg = OPUS_BAD_ARG,
|
|
// gopus_small_buffer = OPUS_BUFFER_TOO_SMALL,
|
|
// gopus_internal = OPUS_INTERNAL_ERROR,
|
|
// gopus_invalid_packet = OPUS_INVALID_PACKET,
|
|
// gopus_unimplemented = OPUS_UNIMPLEMENTED,
|
|
// gopus_invalid_state = OPUS_INVALID_STATE,
|
|
// gopus_alloc_fail = OPUS_ALLOC_FAIL,
|
|
// };
|
|
//
|
|
// enum {
|
|
// gopus_application_voip = OPUS_APPLICATION_VOIP,
|
|
// gopus_application_audio = OPUS_APPLICATION_AUDIO,
|
|
// gopus_restricted_lowdelay = OPUS_APPLICATION_RESTRICTED_LOWDELAY,
|
|
// gopus_bitrate_max = OPUS_BITRATE_MAX,
|
|
// };
|
|
//
|
|
// void gopus_setvbr(OpusEncoder *encoder, int vbr) {
|
|
// opus_encoder_ctl(encoder, OPUS_SET_VBR(vbr));
|
|
// }
|
|
//
|
|
// void gopus_setbitrate(OpusEncoder *encoder, int bitrate) {
|
|
// opus_encoder_ctl(encoder, OPUS_SET_BITRATE(bitrate));
|
|
// }
|
|
//
|
|
// opus_int32 gopus_bitrate(OpusEncoder *encoder) {
|
|
// opus_int32 bitrate;
|
|
// opus_encoder_ctl(encoder, OPUS_GET_BITRATE(&bitrate));
|
|
// return bitrate;
|
|
// }
|
|
//
|
|
// void gopus_setapplication(OpusEncoder *encoder, int application) {
|
|
// opus_encoder_ctl(encoder, OPUS_SET_APPLICATION(application));
|
|
// }
|
|
//
|
|
// opus_int32 gopus_application(OpusEncoder *encoder) {
|
|
// opus_int32 application;
|
|
// opus_encoder_ctl(encoder, OPUS_GET_APPLICATION(&application));
|
|
// return application;
|
|
// }
|
|
//
|
|
// void gopus_encoder_resetstate(OpusEncoder *encoder) {
|
|
// opus_encoder_ctl(encoder, OPUS_RESET_STATE);
|
|
// }
|
|
//
|
|
// void gopus_decoder_resetstate(OpusDecoder *decoder) {
|
|
// opus_decoder_ctl(decoder, OPUS_RESET_STATE);
|
|
// }
|
|
import "C"
|
|
|
|
import (
|
|
"errors"
|
|
"unsafe"
|
|
)
|
|
|
|
type Application int
|
|
|
|
const (
|
|
Voip Application = C.gopus_application_voip
|
|
Audio Application = C.gopus_application_audio
|
|
RestrictedLowDelay Application = C.gopus_restricted_lowdelay
|
|
)
|
|
|
|
const (
|
|
BitrateMaximum = C.gopus_bitrate_max
|
|
)
|
|
|
|
type Encoder struct {
|
|
data []byte
|
|
cEncoder *C.struct_OpusEncoder
|
|
}
|
|
|
|
func NewEncoder(sampleRate, channels int, application Application) (*Encoder, error) {
|
|
encoder := &Encoder{}
|
|
encoder.data = make([]byte, int(C.opus_encoder_get_size(C.int(channels))))
|
|
encoder.cEncoder = (*C.struct_OpusEncoder)(unsafe.Pointer(&encoder.data[0]))
|
|
|
|
ret := C.opus_encoder_init(encoder.cEncoder, C.opus_int32(sampleRate), C.int(channels), C.int(application))
|
|
if err := getErr(ret); err != nil {
|
|
return nil, err
|
|
}
|
|
return encoder, nil
|
|
}
|
|
|
|
func (e *Encoder) Encode(pcm []int16, frameSize, maxDataBytes int) ([]byte, error) {
|
|
pcmPtr := (*C.opus_int16)(unsafe.Pointer(&pcm[0]))
|
|
|
|
data := make([]byte, maxDataBytes)
|
|
dataPtr := (*C.uchar)(unsafe.Pointer(&data[0]))
|
|
|
|
encodedC := C.opus_encode(e.cEncoder, pcmPtr, C.int(frameSize), dataPtr, C.opus_int32(len(data)))
|
|
encoded := int(encodedC)
|
|
|
|
if encoded < 0 {
|
|
return nil, getErr(C.int(encodedC))
|
|
}
|
|
return data[0:encoded], nil
|
|
}
|
|
|
|
func (e *Encoder) SetVbr(vbr bool) {
|
|
var cVbr C.int
|
|
if vbr {
|
|
cVbr = 1
|
|
} else {
|
|
cVbr = 0
|
|
}
|
|
C.gopus_setvbr(e.cEncoder, cVbr)
|
|
}
|
|
|
|
func (e *Encoder) SetBitrate(bitrate int) {
|
|
C.gopus_setbitrate(e.cEncoder, C.int(bitrate))
|
|
}
|
|
|
|
func (e *Encoder) Bitrate() int {
|
|
return int(C.gopus_bitrate(e.cEncoder))
|
|
}
|
|
|
|
func (e *Encoder) SetApplication(application Application) {
|
|
C.gopus_setapplication(e.cEncoder, C.int(application))
|
|
}
|
|
|
|
func (e *Encoder) Application() Application {
|
|
return Application(C.gopus_application(e.cEncoder))
|
|
}
|
|
|
|
func (e *Encoder) ResetState() {
|
|
C.gopus_encoder_resetstate(e.cEncoder)
|
|
}
|
|
|
|
type Decoder struct {
|
|
data []byte
|
|
cDecoder *C.struct_OpusDecoder
|
|
channels int
|
|
}
|
|
|
|
func NewDecoder(sampleRate, channels int) (*Decoder, error) {
|
|
decoder := &Decoder{}
|
|
decoder.data = make([]byte, int(C.opus_decoder_get_size(C.int(channels))))
|
|
decoder.cDecoder = (*C.struct_OpusDecoder)(unsafe.Pointer(&decoder.data[0]))
|
|
|
|
ret := C.opus_decoder_init(decoder.cDecoder, C.opus_int32(sampleRate), C.int(channels))
|
|
if err := getErr(ret); err != nil {
|
|
return nil, err
|
|
}
|
|
decoder.channels = channels
|
|
|
|
return decoder, nil
|
|
}
|
|
|
|
func (d *Decoder) Decode(data []byte, frameSize int, fec bool) ([]int16, error) {
|
|
var dataPtr *C.uchar
|
|
if len(data) > 0 {
|
|
dataPtr = (*C.uchar)(unsafe.Pointer(&data[0]))
|
|
}
|
|
dataLen := C.opus_int32(len(data))
|
|
|
|
output := make([]int16, d.channels*frameSize)
|
|
outputPtr := (*C.opus_int16)(unsafe.Pointer(&output[0]))
|
|
|
|
var cFec C.int
|
|
if fec {
|
|
cFec = 1
|
|
} else {
|
|
cFec = 0
|
|
}
|
|
|
|
cRet := C.opus_decode(d.cDecoder, dataPtr, dataLen, outputPtr, C.int(frameSize), cFec)
|
|
ret := int(cRet)
|
|
|
|
if ret < 0 {
|
|
return nil, getErr(cRet)
|
|
}
|
|
return output[:ret*d.channels], nil
|
|
}
|
|
|
|
func (d *Decoder) ResetState() {
|
|
C.gopus_decoder_resetstate(d.cDecoder)
|
|
}
|
|
|
|
func GetSamplesPerFrame(data []byte, samplingRate int) (int, error) {
|
|
dataPtr := (*C.uchar)(unsafe.Pointer(&data[0]))
|
|
cSamplingRate := C.opus_int32(samplingRate)
|
|
cRet := C.opus_packet_get_samples_per_frame(dataPtr, cSamplingRate)
|
|
return int(cRet), nil
|
|
}
|
|
|
|
func CountFrames(data []byte) (int, error) {
|
|
dataPtr := (*C.uchar)(unsafe.Pointer(&data[0]))
|
|
cLen := C.opus_int32(len(data))
|
|
|
|
cRet := C.opus_packet_get_nb_frames(dataPtr, cLen)
|
|
if err := getErr(cRet); err != nil {
|
|
return 0, err
|
|
}
|
|
return int(cRet), nil
|
|
}
|
|
|
|
var (
|
|
ErrBadArgument = errors.New("bad argument")
|
|
ErrSmallBuffer = errors.New("buffer is too small")
|
|
ErrInternal = errors.New("internal error")
|
|
ErrInvalidPacket = errors.New("invalid packet")
|
|
ErrUnimplemented = errors.New("unimplemented")
|
|
ErrInvalidState = errors.New("invalid state")
|
|
ErrAllocFail = errors.New("allocation failed")
|
|
ErrUnknown = errors.New("unknown error")
|
|
)
|
|
|
|
func getErr(code C.int) error {
|
|
switch code {
|
|
case C.gopus_ok:
|
|
return nil
|
|
case C.gopus_bad_arg:
|
|
return ErrBadArgument
|
|
case C.gopus_small_buffer:
|
|
return ErrSmallBuffer
|
|
case C.gopus_internal:
|
|
return ErrInternal
|
|
case C.gopus_invalid_packet:
|
|
return ErrInvalidPacket
|
|
case C.gopus_unimplemented:
|
|
return ErrUnimplemented
|
|
case C.gopus_invalid_state:
|
|
return ErrInvalidState
|
|
case C.gopus_alloc_fail:
|
|
return ErrAllocFail
|
|
default:
|
|
return ErrUnknown
|
|
}
|
|
}
|
|
EOFOPUS
|
|
log_success "Opus patch applied"
|
|
fi
|
|
|
|
# --- Build TalkKonnect ------------------------------------------------
|
|
echo "[5/7] Building talkkonnect (this may take a few minutes)..."
|
|
cd ~/talkkonnect/cmd/talkkonnect
|
|
|
|
export CGO_ENABLED=1
|
|
export CGO_CFLAGS="$(pkg-config --cflags opus)"
|
|
export CGO_LDFLAGS="$(pkg-config --libs opus) -lm"
|
|
|
|
go build -mod=vendor -v -o ~/talkkonnect-binary . 2>&1 | tail -20
|
|
|
|
if [ ! -f ~/talkkonnect-binary ]; then
|
|
log_error "Build failed!"
|
|
pause
|
|
return 1
|
|
fi
|
|
|
|
# Stop any running instances
|
|
if systemctl is-active --quiet talkkonnect 2>/dev/null; then
|
|
sudo systemctl stop talkkonnect
|
|
fi
|
|
if pgrep -x talkkonnect > /dev/null; then
|
|
sudo pkill -9 talkkonnect
|
|
sleep 1
|
|
fi
|
|
|
|
# Install binary
|
|
sudo cp ~/talkkonnect-binary /usr/local/bin/talkkonnect
|
|
sudo chmod +x /usr/local/bin/talkkonnect
|
|
rm ~/talkkonnect-binary
|
|
log_success "Binary installed to /usr/local/bin/talkkonnect"
|
|
|
|
# --- User Permissions -------------------------------------------------
|
|
echo "[6/7] Setting up permissions..."
|
|
if ! groups "$TARGET_USER" | grep -q input; then
|
|
sudo usermod -a -G input "$TARGET_USER"
|
|
log_success "Added $TARGET_USER to 'input' group"
|
|
fi
|
|
if ! groups "$TARGET_USER" | grep -q audio; then
|
|
sudo usermod -a -G audio "$TARGET_USER"
|
|
log_success "Added $TARGET_USER to 'audio' group"
|
|
fi
|
|
|
|
# --- Configuration ----------------------------------------------------
|
|
echo "[7/7] Creating configuration..."
|
|
|
|
# Create config directory as target user
|
|
if [ "$TARGET_USER" != "$USER" ]; then
|
|
sudo -u "$TARGET_USER" mkdir -p "$CONFIG_DIR"
|
|
else
|
|
mkdir -p "$CONFIG_DIR"
|
|
fi
|
|
|
|
# Create config file with provided settings
|
|
sudo tee "$CONFIG_DIR/talkkonnect.xml" > /dev/null <<EOFXML
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<document type="talkkonnect/xml">
|
|
<global>
|
|
<software>
|
|
<settings outputdevice="default"
|
|
logfilenameandpath="$CONFIG_DIR/talkkonnect.log"
|
|
logging="both"
|
|
daemonize="false"
|
|
cancelconnect="false"
|
|
simplexwithvox="false"
|
|
nextserverindex="0"/>
|
|
<autoprovisioning enabled="false"/>
|
|
</software>
|
|
<hardware>
|
|
<settings targetboard="pc" voiceactivitytimersecs="200"/>
|
|
<io>
|
|
<pins enabled="false"/>
|
|
</io>
|
|
</hardware>
|
|
</global>
|
|
|
|
<accounts>
|
|
<account name="default" default="true">
|
|
<serverandport>$server_addr:$server_port</serverandport>
|
|
<username>$tk_user</username>
|
|
<password>$tk_pass</password>
|
|
<insecure>true</insecure>
|
|
<register>false</register>
|
|
<certificate></certificate>
|
|
<channel>$tk_channel</channel>
|
|
<ident></ident>
|
|
<tokens enabled="false"></tokens>
|
|
<voicetargets enabled="false">
|
|
<id id="1" iscurrent="false" name="default">
|
|
<channels></channels>
|
|
<users></users>
|
|
</id>
|
|
</voicetargets>
|
|
</account>
|
|
</accounts>
|
|
|
|
<beacon enabled="false"/>
|
|
|
|
<audio>
|
|
<input>
|
|
<settings enabled="true" device="default" samplerate="48000"
|
|
channels="1" codec="opus" framespersecond="50"/>
|
|
</input>
|
|
<output>
|
|
<settings enabled="true" device="default" samplerate="48000" channels="1"/>
|
|
</output>
|
|
</audio>
|
|
|
|
<ptt enabled="false">
|
|
<usbkeyboard enabled="false"/>
|
|
</ptt>
|
|
|
|
<voiceactivity enabled="true">
|
|
<settings threshold="0.3" holdtimems="1000" holdtimeoutms="2000"/>
|
|
</voiceactivity>
|
|
|
|
<sounds enabled="false"/>
|
|
<txtts enabled="false"/>
|
|
<smtp enabled="false"/>
|
|
<api enabled="false"/>
|
|
<printxml enabled="false"/>
|
|
|
|
</document>
|
|
EOFXML
|
|
|
|
# Set ownership
|
|
sudo chown -R "$TARGET_USER:$TARGET_USER" "$CONFIG_DIR"
|
|
sudo chmod 755 "$CONFIG_DIR"
|
|
sudo chmod 644 "$CONFIG_DIR/talkkonnect.xml"
|
|
|
|
# Create systemd service
|
|
sudo tee /etc/systemd/system/talkkonnect.service > /dev/null <<EOFSVC
|
|
[Unit]
|
|
Description=TalkKonnect Headless Mumble Transceiver
|
|
After=network-online.target sound.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=$TARGET_USER
|
|
Group=$TARGET_USER
|
|
WorkingDirectory=$TARGET_HOME
|
|
ExecStart=/usr/local/bin/talkkonnect -config $CONFIG_DIR/talkkonnect.xml
|
|
Restart=always
|
|
RestartSec=5
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
Environment="XDG_RUNTIME_DIR=/run/user/$TARGET_UID"
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOFSVC
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable talkkonnect
|
|
sudo systemctl start talkkonnect
|
|
|
|
sleep 3
|
|
|
|
log_success "talkkonnect installed successfully!"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo " Server: $server_addr:$server_port"
|
|
echo " Username: $tk_user"
|
|
echo " Channel: $tk_channel"
|
|
echo " Config: $CONFIG_DIR/talkkonnect.xml"
|
|
echo " Binary: /usr/local/bin/talkkonnect"
|
|
echo "═══════════════════════════════════════════════════════════════"
|
|
echo
|
|
|
|
# Show status
|
|
if systemctl is-active --quiet talkkonnect; then
|
|
log_success "Service is running"
|
|
echo "View logs: sudo journalctl -u talkkonnect -f"
|
|
else
|
|
log_warning "Service may have issues"
|
|
echo "Check logs: sudo journalctl -u talkkonnect -n 50"
|
|
fi
|
|
|
|
pause
|
|
}
|