From d544c5a7027138be97b804307c77dbf8fc99d80b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 23:24:03 +0000 Subject: [PATCH 1/3] Fix talkkonnect x86_64 build failure with Opus patch Critical fixes to resolve "[ERROR] Binary not executable after build": 1. **Opus x86_64 Compatibility Patch** - Added gopus library patch to use system libopus for x86_64 - The embedded Opus source in gopus is ARM-optimized and incomplete for x86_64 - Now uses pkg-config to link against system libopus library 2. **Vendored Dependencies** - Added `go mod vendor` to create vendored dependencies - Build now uses `-mod=vendor` flag to ensure patched gopus is used 3. **Enhanced Build Process** - Set CGO_CFLAGS and CGO_LDFLAGS for proper Opus compilation - Build in cmd/talkkonnect directory (correct location) - Added build-essential and pkg-config to dependencies 4. **Improved XML Configuration** - Updated to modern talkkonnect/xml document type - Added critical voicetargets section to prevent crashes - Added proper global settings structure - Created .config/talkkonnect directory for logs 5. **Additional Dependencies** - Added libopus0, libopusfile-dev for complete Opus support - Added build-essential and pkg-config for compilation This integrates the working approach from talkkonnect_complete_install.sh into the setup_intercom.sh script. --- setup_intercom.sh | 375 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 336 insertions(+), 39 deletions(-) diff --git a/setup_intercom.sh b/setup_intercom.sh index 8f6ccbc..4a23e21 100755 --- a/setup_intercom.sh +++ b/setup_intercom.sh @@ -217,7 +217,7 @@ install_talkkonnect_with_config() { fi echo "[2/4] Installing audio dependencies..." - sudo apt install -y libopenal-dev libopus-dev alsa-utils portaudio19-dev git + sudo apt install -y libopenal-dev libopus-dev libopus0 libopusfile-dev alsa-utils portaudio19-dev git build-essential pkg-config echo "[3/4] Cloning and building talkkonnect..." local tk_src="/tmp/talkkonnect-src" @@ -237,8 +237,273 @@ install_talkkonnect_with_config() { cd '$tk_src' || exit 1 - echo 'Compiling...' - go build -v -o /home/$KIOSK_USER/go/bin/talkkonnect . 2>&1 | tail -20 + echo 'Creating vendored dependencies...' + go mod vendor + + # Apply critical x86_64 Opus patch + echo 'Applying x86_64 Opus compatibility patch...' + if [[ -f vendor/github.com/talkkonnect/gopus/opus_nonshared.go ]]; then + cp vendor/github.com/talkkonnect/gopus/opus_nonshared.go \ + vendor/github.com/talkkonnect/gopus/opus_nonshared.go.backup + + 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 +// #include +// #include +// +// 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 + echo 'Opus patch applied' + else + echo 'Warning: gopus not found in vendor directory' + fi + + # Set CGO flags for Opus + export CGO_CFLAGS=\"\$(pkg-config --cflags opus)\" + export CGO_LDFLAGS=\"\$(pkg-config --libs opus) -lm\" + + # Build in the cmd/talkkonnect directory with vendored dependencies + cd cmd/talkkonnect || exit 1 + + echo 'Compiling with vendored dependencies...' + go build -mod=vendor -v -o /home/$KIOSK_USER/go/bin/talkkonnect . 2>&1 | tail -20 if [[ -f /home/$KIOSK_USER/go/bin/talkkonnect ]]; then chmod +x /home/$KIOSK_USER/go/bin/talkkonnect @@ -275,59 +540,91 @@ install_talkkonnect_with_config() { echo "[4/4] Creating configuration..." + # Create config directory for logs + sudo -u "$KIOSK_USER" mkdir -p /home/$KIOSK_USER/.config/talkkonnect + sudo -u "$KIOSK_USER" tee /home/$KIOSK_USER/talkkonnect.xml > /dev/null < - + + + + + + + + + + + + + + - - true - Primary - $server_addr + + $server_addr:$server_port $tk_user $tk_pass true - $server_port + false + $tk_channel + + + + + + + + - - - default - 3 - false - true - - - - - false - rpi - - - - + + + + + + + + + + + + + + + + + + + + TKXML From d16ca7a408292159770765454a8ee26e7aebc7f3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 23:39:37 +0000 Subject: [PATCH 2/3] Fix vendor directory permission denied error Added chown after git clone to give kiosk user ownership of the /tmp/talkkonnect-src directory. This allows 'go mod vendor' to successfully create the vendor directory when running as kiosk user. Without this fix, the build would fail with: "go: mkdir /tmp/talkkonnect-src/vendor: permission denied" "Warning: gopus not found in vendor directory" "[ERROR] Binary not executable after build" --- setup_intercom.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup_intercom.sh b/setup_intercom.sh index 4a23e21..ab4369e 100755 --- a/setup_intercom.sh +++ b/setup_intercom.sh @@ -224,6 +224,9 @@ install_talkkonnect_with_config() { rm -rf "$tk_src" git clone https://github.com/talkkonnect/talkkonnect.git "$tk_src" + # Change ownership so kiosk user can write to vendor directory + sudo chown -R "$KIOSK_USER:$KIOSK_USER" "$tk_src" + echo "Building (this takes 5-10 minutes)..." # Build as kiosk user with proper environment From e6d03745389fa2c62b9dd59389b13a323abac221 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 23:50:15 +0000 Subject: [PATCH 3/3] Use kiosk home directory for talkkonnect build (fixes permission errors) Changed from /tmp to ~/talkkonnect to match working script approach: - Clone to /home/$KIOSK_USER/talkkonnect instead of /tmp/talkkonnect-src - Build to ~/talkkonnect-binary then copy to ~/go/bin/talkkonnect - Run all operations as kiosk user from the start - No chown needed - user owns their own home directory This eliminates all "Permission denied" errors when: - Creating vendor directory - Removing old source files - Building the binary Matches the proven approach from talkkonnect_complete_install.sh --- setup_intercom.sh | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/setup_intercom.sh b/setup_intercom.sh index ab4369e..1b44b4e 100755 --- a/setup_intercom.sh +++ b/setup_intercom.sh @@ -220,16 +220,10 @@ install_talkkonnect_with_config() { sudo apt install -y libopenal-dev libopus-dev libopus0 libopusfile-dev alsa-utils portaudio19-dev git build-essential pkg-config echo "[3/4] Cloning and building talkkonnect..." - local tk_src="/tmp/talkkonnect-src" - rm -rf "$tk_src" - git clone https://github.com/talkkonnect/talkkonnect.git "$tk_src" - - # Change ownership so kiosk user can write to vendor directory - sudo chown -R "$KIOSK_USER:$KIOSK_USER" "$tk_src" echo "Building (this takes 5-10 minutes)..." - # Build as kiosk user with proper environment + # Build as kiosk user with proper environment (using their home directory to avoid permission issues) sudo -u "$KIOSK_USER" bash -c " export PATH=/usr/local/go/bin:\$PATH export HOME=/home/$KIOSK_USER @@ -238,7 +232,16 @@ install_talkkonnect_with_config() { mkdir -p /home/$KIOSK_USER/go/bin - cd '$tk_src' || exit 1 + # Clone to kiosk user's home directory (avoids /tmp permission issues) + cd /home/$KIOSK_USER || exit 1 + + if [[ -d talkkonnect ]]; then + echo 'Removing existing talkkonnect directory...' + rm -rf talkkonnect + fi + + git clone https://github.com/talkkonnect/talkkonnect.git + cd talkkonnect || exit 1 echo 'Creating vendored dependencies...' go mod vendor @@ -506,10 +509,10 @@ EOFOPUS cd cmd/talkkonnect || exit 1 echo 'Compiling with vendored dependencies...' - go build -mod=vendor -v -o /home/$KIOSK_USER/go/bin/talkkonnect . 2>&1 | tail -20 + go build -mod=vendor -v -o /home/$KIOSK_USER/talkkonnect-binary . 2>&1 | tail -20 - if [[ -f /home/$KIOSK_USER/go/bin/talkkonnect ]]; then - chmod +x /home/$KIOSK_USER/go/bin/talkkonnect + if [[ -f /home/$KIOSK_USER/talkkonnect-binary ]]; then + chmod +x /home/$KIOSK_USER/talkkonnect-binary echo 'Build successful' exit 0 else @@ -519,7 +522,6 @@ EOFOPUS " local build_result=$? - rm -rf "$tk_src" if [[ $build_result -ne 0 ]]; then log_error "Build failed" @@ -532,13 +534,19 @@ EOFOPUS return 1 fi - # Verify binary - if [[ ! -x "/home/$KIOSK_USER/go/bin/talkkonnect" ]]; then + # Verify and install binary + if [[ ! -x "/home/$KIOSK_USER/talkkonnect-binary" ]]; then log_error "Binary not executable after build" pause return 1 fi + echo "Installing binary..." + sudo cp /home/$KIOSK_USER/talkkonnect-binary /home/$KIOSK_USER/go/bin/talkkonnect + sudo chmod +x /home/$KIOSK_USER/go/bin/talkkonnect + sudo chown "$KIOSK_USER:$KIOSK_USER" /home/$KIOSK_USER/go/bin/talkkonnect + rm -f /home/$KIOSK_USER/talkkonnect-binary + log_success "talkkonnect built successfully" echo "[4/4] Creating configuration..."