# StreamZet Linux SDK — integration preview

Documentation revision: 2026-09-22

https://streamzet.com/docs

## Linux 시작하기

일반 Linux 앱에서 Go API 또는 C ABI를 테스트합니다. 전달 공유 라이브러리는 Ubuntu 24.04 arm64 전용입니다.

### 전제 조건과 범위

root가 아닌 일반 사용자로 실행합니다. 터미널에 /bin/bash와 PTY가 필요합니다. 데스크톱 모드는 일치하는 캡처 도우미와 의존성을 배포해야 하며, 헤드리스 환경은 터미널만 제공합니다. Fleet은 명시적으로 켜기 전에는 비활성입니다. 레거시 ARMv5 xSpan에는 사용할 수 없습니다.

백엔드 HTTPS·시그널링 WSS 주소, DNS, 정확한 시간, 외부 ICE/TURN 통신이 필요합니다. relay_only=true는 사용 가능한 TURN 인증·연결이 필요합니다. SDK가 외부 수신 리스너를 설치하지 않습니다.

### C 테스트 앱 실행

```json
{
  "config": {
    "supabase_url": "https://YOUR_BACKEND",
    "publishable_key": "YOUR_PUBLIC_ANON_KEY",
    "signal_url": "wss://YOUR_SIGNALING",
    "device_name": "Store Linux",
    "mode": "terminal"
  },
  "state_dir": "/home/operator/.local/state/streamzet-sdk-test",
  "fleet": false
}
```

```sh
chmod 600 config.json
./embedded-example config.json
```

C 예제는 시작 상태와 이후 변경된 상태를 500ms 간격으로 확인해 출력하고 Enter 또는 표준입력 종료로 멈춥니다. running=true는 등록·연결 완료가 아닙니다. device_code가 생긴 뒤 대시보드에서 페어링하세요. registration_key는 선택적 비공개 테넌트 등록용입니다.

- [단계별 예제 사용 가이드](https://streamzet.com/docs/linux/test-apps)

### Go 연동

```go
support, err := sdk.Start(ctx, sdk.Options{
    Config: sdk.Config{
        SupabaseURL: "https://YOUR_BACKEND",
        PublishableKey: "YOUR_PUBLIC_ANON_KEY",
        SignalURL: "wss://YOUR_SIGNALING",
        DeviceName: "Store Linux", Mode: "terminal",
    },
    StateDir: "/home/operator/.local/state/streamzet-sdk",
    Fleet: false,
})
if err != nil { return err }
// The host decides when support is enabled and when to stop.
// Poll support.Identity() for public registration information.
// Later, from a worker during host shutdown:
support.Stop()
return support.Wait()
```

github.com/plitsoft/streamzet/linux-agent/sdk를 import합니다. 태그 릴리스 전에는 전달 소스를 가리키는 로컬 module replace를 사용합니다. Start는 지원을 활성화하며 로컬 동의 창을 표시하지 않습니다. 제품에서 지원 정책과 제어 UI를 제공해야 합니다.

### 빌드와 수명주기

```sh
make sdk
# Outputs: dist/sdk/native/libstreamzet.so, libstreamzet.h, embedded-example
```

대상 Linux ABI에서 Go 1.26·C 컴파일러로 빌드합니다. 생성된 헤더를 쓰고 libstreamzet.so를 예제 옆에 둡니다. 호스트 수명 중 Go 라이브러리를 dlclose하지 마세요. 프로세스당 한 번 시작하고 종료 전 stop·wait하세요. 업데이트 시 상태를 유지하고 단독 에이전트와 공유하지 마세요. 임베드 모드는 단독 업데이터 실행이나 호스트 실행파일 교체를 하지 않습니다.

## Linux 앱에 연동하기

C ABI·Go SDK의 빌드, 설정, 메모리 소유권과 종료 처리를 실행 가능한 예제로 확인합니다.

### 1. 대상과 준비물

현재 Linux SDK는 통합 검토용 preview입니다. Ubuntu 24.04의 일반 사용자 실행을 기준으로 하며 xSpan의 구형 ARMv5/Linux 2.6 제품 SDK와 별개입니다. arm64/amd64는 각각 대상 ABI에서 빌드해야 합니다. macOS에서 만든 .so를 Linux에 복사해 사용할 수 없습니다.

Go 1.26.x, C 컴파일러, make, Bash, CA 인증서 저장소가 필요합니다. 대상 시스템의 아키텍처와 glibc 버전을 먼저 기록하세요. TLS 검증을 끄지 말고 배포판 ca-certificates와 필요한 사내 CA를 올바르게 설치합니다.

```sh
uname -m
getconf GNU_LIBC_VERSION
go version
sudo apt-get update
sudo apt-get install --no-install-recommends build-essential ca-certificates bash
# Run application builds and support as your normal user:
cd source
make sdk
```

### 2. 설정과 기기 상태 디렉터리

```json
{
  "config": {
    "supabase_url": "https://YOUR_BACKEND",
    "publishable_key": "YOUR_PUBLIC_ANON_KEY",
    "signal_url": "wss://YOUR_SIGNALING",
    "device_name": "SDK integration test",
    "mode": "terminal"
  },
  "state_dir": "/home/operator/.local/state/streamzet-sdk-test",
  "fleet": false
}
```

YOUR_*를 제공받은 공개 연결 값으로 바꾸고 state_dir를 현재 사용자 전용의 절대 경로로 바꿉니다. JSON 안의 ~나 $HOME은 확장되지 않습니다. 디렉터리는 0700, identity.json은 0600이며 다른 SDK/에이전트와 공유하지 않습니다. 복제 이미지에 identity.json을 넣지 마세요.

```sh
chmod 600 config.json
mkdir -p /home/operator/.local/state/streamzet-sdk-test
chmod 700 /home/operator/.local/state/streamzet-sdk-test
./dist/sdk/native/embedded-example config.json
# Press Enter for a graceful stop.
```

> Start는 원격 지원을 활성화합니다. Linux SDK는 자체 동의 UI를 띄우지 않으므로 호스트 앱에서 사용자 안내·시작·종료 정책을 구현해야 합니다. Fleet 명령 실행은 기본 false이며 필요한 제품에서만 명시적으로 활성화합니다.

### 3. C/C++에서 사용하기

생성된 libstreamzet.h와 같은 빌드의 libstreamzet.so를 한 쌍으로 사용합니다. 아래 명령은 SDK source 디렉터리 기준이며 실행파일 옆의 라이브러리를 찾는 rpath를 설정합니다. Windows DLL이나 xSpan 정적 라이브러리와 호환되지 않습니다.

```sh
cc -Wall -Wextra -Werror -I dist/sdk/native examples/embedded.c \
  -L dist/sdk/native -lstreamzet -Wl,-rpath,'$ORIGIN' \
  -o dist/sdk/native/embedded-example
ldd dist/sdk/native/embedded-example
./dist/sdk/native/embedded-example config.json
```

```c
#include "libstreamzet.h"
#include <stdio.h>

/* Call from a worker thread; config_json is NUL-terminated UTF-8. */
int run_support(char *config_json) {
    int rc = StreamzetStart(config_json);
    char *status = StreamzetStatus();
    puts(status);
    StreamzetFree(status); /* Exactly once, including error paths. */
    if (rc != 0) return rc;
    /* The real host continues its work here until support should end. */
    getchar();
    StreamzetStop(); /* Waits for peers/background work; not a UI callback. */
    return 0;
}
```

Start 반환값은 0=시작됨, 1=잘못된 설정/시작 실패, 2=이미 시작됨입니다. JSON 입력은 호출 중 복사하며 호출자 소유입니다. Status 문자열은 호출자에게 소유권을 넘기므로 StreamzetFree로 한 번 해제합니다. Stop은 동기적·반복 호출 가능하며 프로세스 수명 중 dlclose하지 않습니다.

### 4. Go 호스트에 연결

현재 공개 버전 태그를 가정한 go get 명령은 제공하지 않습니다. 전달 ZIP의 source를 vendor/streamzet-linux에 두고 호스트 모듈에 로컬 replace를 추가합니다. internal 패키지를 직접 import하지 않습니다.

```sh
# Run inside your existing host Go module:
go mod edit -require=github.com/plitsoft/streamzet/linux-agent@v0.0.0
go mod edit -replace=github.com/plitsoft/streamzet/linux-agent=./vendor/streamzet-linux
# After adding the SDK import to your host:
go mod tidy
# Or run the complete example shipped in the source package:
cd vendor/streamzet-linux
go run ./examples/go /absolute/path/config.json
```

```go
import (
    "context"
    "github.com/plitsoft/streamzet/linux-agent/sdk"
)

func startSupport(ctx context.Context, options sdk.Options) (*sdk.Support, error) {
    support, err := sdk.Start(ctx, options)
    if err != nil { return nil, err }
    // support.Identity() returns only device ID/code; enrollment is asynchronous.
    // Keep support while the host performs its normal work.
    return support, nil
}

func stopSupport(support *sdk.Support) error {
    support.Stop() // Requests cancellation; nonblocking.
    return support.Wait() // Join before host shutdown, off the UI thread.
}
```

완전한 실행 예제는 examples/go/main.go입니다. 컨텍스트 취소와 Stop은 종료 요청이며 Done/Wait가 완료를 확인합니다. 프로세스당 한 인스턴스만 허용합니다. SDK는 호스트의 signal handler를 바꾸거나 os.Exit를 호출하거나 업데이터를 설치하지 않습니다.

### 5. 데스크톱 캡처를 추가할 때

처음에는 mode=terminal로 연결과 종료를 확인합니다. GUI가 필요하면 해당 로그인 사용자의 그래픽 세션 안에서 mode=auto로 실행합니다. DISPLAY, WAYLAND_DISPLAY, XDG_RUNTIME_DIR, DBUS_SESSION_BUS_ADDRESS를 임의로 다른 사용자 것으로 바꾸지 않습니다.

```sh
sudo apt-get install --no-install-recommends pkg-config libgstreamer1.0-dev \
  libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base \
  gstreamer1.0-plugins-good gstreamer1.0-pipewire wl-clipboard
mkdir -p dist/sdk/native
cc -O2 -Wall -Wextra -Werror native/capture.c \
  -o dist/sdk/native/streamzet-capture \
  $(pkg-config --cflags --libs gstreamer-app-1.0)
# Set config.capture_helper to the ABSOLUTE path of streamzet-capture.
# Set config.mode to "auto".
```

Wayland는 PipeWire와 해당 데스크톱의 xdg-desktop-portal 구현이 필요하고 사용자가 공유/원격 제어를 승인해야 합니다. 거부하면 데스크톱 제어가 생기지 않으며 터미널 연결은 별도로 유지될 수 있습니다. X11도 실제 디스플레이 권한과 GStreamer 플러그인을 확인합니다. 파일 전송은 이 SDK의 지원 기능이 아닙니다.

### 6. 전달과 운영

호스트와 함께 대상 ABI의 라이브러리, 생성 헤더, 필요한 캡처 헬퍼, 라이선스와 공개 설정 주입 절차를 전달합니다. 상태 디렉터리는 업데이트 때 보존하고 백업 권한을 제한합니다. 대시보드 온라인·연결 상태와 SDK의 running을 같은 의미로 취급하지 마세요.

- [Linux 예제로 실제 기능 확인하기](https://streamzet.com/docs/linux/test-apps)

## Linux API 명세

Go 필드·C JSON 이름·반환 코드·메모리 소유권. 작업 시작 전에 설정을 검증합니다.

### sdk.Options / 최상위 JSON

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| Config / config | sdk.Config · required | 아래 연결·기능 설정. Start에서 복사본을 검증·기본값 처리. |
| StateDir / state_dir | string · required | 소유한 절대 경로 디렉터리, 0700, 심볼릭 링크 불가. 없으면 생성. 파일 잠금으로 동시 ID 사용 금지. 업데이트 시 유지하고 타 기기로 복제 금지. |
| Fleet / fleet | bool · false | 지원 실행 중 호스트 OS 사용자 권한의 원격 일괄 명령 허용 여부. |

### sdk.Config / config

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| SupabaseURL / supabase_url | string · required | HTTPS origin. 포트·끝 / 허용. 자격 정보·하위 경로·쿼리(빈 ? 포함)·fragment 금지. |
| PublishableKey / publishable_key | string · required | 공백이 아닌 공개/anon 키. service-role 금지. |
| RegistrationKey / registration_key | string · "" | 선택적 테넌트 등록. sz_live_ 접두사·최대 128바이트. 등록에만 전송. 빈 값이면 기기 코드 연결. |
| SignalURL / signal_url | string · required | SupabaseURL과 같은 제한을 따르는 WSS origin. |
| DeviceName / device_name | string · hostname | 빈 값은 os.Hostname(). 최종 이름은 공백 불가·UTF-8 최대 128바이트. |
| CaptureHelper / capture_helper | string · /usr/lib/streamzet/streamzet-capture | 캡처 도우미 절대 경로. terminal 모드도 경로 형식 검증. 파일 존재 여부는 Start가 아닌 캡처 시작 시 확인. |
| Mode / mode | string · auto | 빈 값은 auto. auto는 현재 사용자 데스크톱 탐지, terminal은 터미널 전용. 다른 값 거부. |
| RelayOnly / relay_only | bool · false | true는 WebRTC를 릴레이 후보로 제한하며 TURN 필요. false는 직접·릴레이 경로 허용. |

### Go 수명주기

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| Start(ctx, options) | (*Support, error) | ctx: nil·취소되지 않은 context.Context. options: sdk.Options. root·설정/경로 오류·프로세스/상태 중복·파일/ID 실패 거부. ErrRunning은 같은 프로세스의 기존 SDK. 성공 후 비동기 등록. 서비스·시그널 핸들러 설치 없음. |
| Support.Stop() | void | 반복 가능한 비차단 취소. 종료 완료를 보장하지 않습니다. |
| Support.Wait() | error | 작업 종료까지 차단. 정상 종료 nil, 그 외 최종 오류. GUI 스레드 호출 금지. |
| Support.Done() | <-chan struct{} | 작업 정리 완료 시 닫히는 읽기 전용 채널. |
| Support.Identity() | Identity | 스레드 안전 공개 스냅샷. 비밀 제외. DeviceCode는 등록 후, DeviceID는 등록 전에도 존재 가능. 접속 상태 아님. |

### C ABI

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| StreamzetStart(char *json) | int | NUL 종료 UTF-8 JSON. 반환 전 복사, 입력은 호출자 소유. 알 수 없는 필드·추가 JSON 거부. 0 시작, 1 설정/시작 오류, 2 이미 시작된 핸들(작업 종료 후에도 재사용 전 Stop). |
| StreamzetStatus() | char * · owned JSON | 아래 필드의 스냅샷. 반환 포인터는 StreamzetFree로 정확히 한 번 해제. Start/Stop과 직렬화되어 Stop 중 Status가 대기할 수 있습니다. |
| StreamzetStop() | void | 직렬화·반복 가능·차단. 종료 요청·대기·핸들 해제. 작업 스레드에서 호출. ID는 삭제하지 않습니다. |
| StreamzetFree(char *value) | void | Status 반환 포인터 또는 NULL만 전달. 중복 해제·호출자/Go 포인터 전달 금지. |

### Identity와 C 상태 필드

| 필드 / API | 형식 / 기본값 | 계약 |
| --- | --- | --- |
| running | boolean · false | 작업이 살아 있음. 온라인·등록·운영자 연결 상태와 다릅니다. |
| error | string · "" | 최근 동기 Start 실패 또는 Stop이 수집한 오류. 빈 값은 기록된 오류 없음이며 통신 성공이 아닙니다. |
| identity | object · omitted | C 핸들이 있으면 제공. Start 전·Stop 후 생략. secret·instance_id 미노출. |
| Identity.DeviceID / identity.device_id | string | 등록 전에 생성되는 영구 공개 UUID. |
| Identity.DeviceCode / identity.device_code | string · "" | 등록 후 연결 코드. 미등록이면 빈 값. |

## Linux 예제 사용 가이드

C와 Go 예제로 등록, 웹 터미널, 화면 공유와 종료를 순서대로 확인합니다.

### 1. 테스트 준비

Ubuntu 24.04 일반 사용자 계정에서 실행하세요. C 예제 embedded-example과 Go 예제 go-example은 콘솔 앱입니다. Android APK처럼 설치 화면이나 설정 창이 없으며 config.json을 파일로 받습니다. root/sudo 실행은 거부합니다.

1. 전달 ZIP의 SHA-256과 내부 SHA256SUMS를 검사합니다. CPU 아키텍처와 배포판을 확인하고, 일치하지 않는 바이너리는 대상 장비에서 make sdk로 다시 빌드합니다.
2. backend/public anon key/signaling 정보를 받고 전용 state_dir를 설정합니다. config.json은 0600, 상태 폴더는 0700으로 제한합니다.
3. 처음에는 mode=terminal, fleet=false로 시작합니다. 같은 상태 폴더로 두 예제를 동시에 실행하지 않습니다.

- [설치 패키지·완전한 JSON·빌드 명령](https://streamzet.com/docs/linux/integration)

### 2. C 예제 실행

```sh
cd source
make sdk
chmod 600 /absolute/path/config.json
./dist/sdk/native/embedded-example /absolute/path/config.json
```

첫 JSON에서 running=true이면 SDK 워커가 시작된 것입니다. 등록이 완료되면 identity.device_code가 채워진 새 JSON이 출력됩니다. 입력 파일에 잘못된 필드가 있거나 root로 실행하면 error와 실패 종료 코드가 나옵니다. 화면에 명령을 입력하는 콘솔이 아니라 SDK 수명주기를 보여주는 예제입니다.

```json
{"running":true,"error":"","identity":{"device_id":"<device UUID>","device_code":""}}
{"running":true,"error":"","identity":{"device_id":"<device UUID>","device_code":"<your 8-character code>"}}
```

> 위 JSON은 설명용입니다. running은 온라인·뷰어 연결 플래그가 아닙니다. Enter를 누르거나 표준입력이 닫히면 예제가 종료됩니다.

### 3. 웹에서 터미널 왕복 확인

1. 승인된 운영자가 Devices → Pair Device에서 표시된 코드를 연결하고 해당 Linux 기기의 제어 세션을 엽니다. 모니터 모드는 터미널 입력을 허용하지 않습니다.
2. 웹 터미널에 아래 printf와 whoami를 입력합니다. SDK를 실행한 일반 사용자로 실행되는지, 한글과 영문이 돌아오는지 확인합니다. 실제 운영 명령이나 민감한 파일은 사용하지 않습니다.
3. 브라우저 창 크기를 바꿔 stty size 결과가 갱신되는지 확인합니다. exit로 셸을 닫은 뒤 New terminal로 다시 여는 동작을 확인합니다.

```sh
printf 'SDK_%s_OK\n' LINUX
printf '한글 UTF-8 확인\n'
whoami
stty size
```

![웹 터미널의 실제 SDK_LINUX_OK 응답](https://streamzet.com/docs-assets/linux/images/web-terminal.png)

Ubuntu 테스트 컨테이너에서 실행한 SDK와 운영 웹 뷰어의 실제 왕복 결과입니다.

### 4. Go 예제와 등록 보존

1. C 예제 창에서 Enter를 눌러 running=false와 정상 종료를 확인합니다. 웹 화면의 종료 버튼은 해당 peer 연결을 종료하며 SDK 워커의 Start/Stop과는 별개입니다.
2. 같은 config.json과 state_dir로 Go 예제를 실행합니다. 같은 device_id/device_code가 유지되는지 확인한 뒤 대시보드에서 다시 연결합니다.
3. Enter로 Go 예제를 종료합니다. 실제 앱에 넣을 때는 UI 스레드 밖에서 Stop 후 Wait 완료를 기다리고, 호스트 작업은 지원이 끝나도 유지되도록 구현합니다.

```sh
./dist/sdk/native/go-example /absolute/path/config.json
# Equivalent source run:
go run ./examples/go /absolute/path/config.json
```

### 5. X11 / Wayland 화면 확인

캡처 헬퍼와 그래픽 런타임을 설치한 후 config.mode를 auto로 바꾸고 capture_helper를 올바른 절대 경로로 지정합니다. 데스크톱에 로그인한 바로 그 사용자로 실행합니다. headless 기기는 터미널 전용이며 검은 영상이 나온다고 화면 기능이 있다고 간주하지 않습니다.

1. X11: 테스트 편집기를 열어 영상 변화, 포인터, 클릭, 드래그, 스크롤과 문자 입력을 확인합니다. 접근 권한이 없는 디스플레이를 강제로 열지 않습니다.
2. Wayland: 시스템의 화면공유/원격 제어 창에서 대상 화면과 원격 입력을 승인합니다. 대문자·기호·한글을 시험합니다. 텍스트 전송은 기기의 클립보드를 사용할 수 있습니다.
3. Wayland 거부도 별도로 시험합니다. 화면/입력은 허용되지 않아야 하고, 정책상 열어 둔 터미널은 계속 동작할 수 있습니다. 사용자는 SDK 전체를 멈춰 터미널까지 종료할 수 있어야 합니다.

![GNOME Wayland의 화면과 원격 입력 승인](https://streamzet.com/docs-assets/linux/images/wayland-consent.png)

실제 GNOME portal. 원격 입력이 필요하면 Allow Remote Interaction을 켜고 Share를 선택합니다.

### 문제 해결

| 증상 | 확인과 조치 |
| --- | --- |
| root remote shells are not supported | sudo를 제거하고 일반 사용자 계정으로 실행합니다. |
| state directory must be absolute/private | JSON의 경로를 실제 절대 경로로 지정하고 소유자·0700 권한·심볼릭 링크 여부를 확인합니다. |
| another agent owns this identity / already running | 다른 인스턴스를 정상 종료하고 재시도합니다. lock/identity 파일을 실행 중에 삭제하지 않습니다. |
| libstreamzet.so를 찾을 수 없음 | 같은 ABI의 .so를 실행파일 옆에 놓고 제공된 rpath 빌드 명령을 사용합니다. ldd로 확인합니다. |
| running=true, 기기 코드 없음 | 등록은 비동기입니다. DNS·HTTPS·CA 저장소·공개 키·방화벽을 확인하고 상태를 기다립니다. TLS 검증을 끄지 않습니다. |
| 시작 직후 예제 종료 | stdin이 닫히면 정상 종료합니다. 터미널에서 실행하거나 컨테이너에 -i/-t를 유지합니다. 서비스 구현은 SDK 수명주기를 호스트에서 관리합니다. |
| Wayland 화면 불가 | 로그인 세션·portal backend·PipeWire·승인·capture_helper를 확인합니다. 터미널 성공과 데스크톱 성공은 별개입니다. |

### 인수 기록과 지원 요청

SDK 빌드/ZIP SHA-256, 배포판·kernel·CPU·glibc, X11/Wayland 세션, 호스트 사용자, 네트워크 경로, 기대/실제 결과와 재현 절차를 기록합니다. 테스트 후 모든 viewer를 종료하고 SDK Stop/Wait 또는 C Stop의 완료를 확인합니다. 설정 파일의 비밀 등록 키와 identity.json은 외부에 첨부하지 않습니다.
