
P2P, 종단 간 암호화 채팅. 받은편지함 없음. 복구할 계정 없음. 아무도 듣지 않습니다 — 우리조차도요.
메시지는 libp2p를 통해 피어 간에 직접 전송되며, 기기를 떠나기 전에 Signal 스타일의 Olm/Megolm 프로토콜(vodozemac을 통해)로 암호화됩니다. 관여하는 유일한 서버는 피어가 서로의 현재 주소를 찾도록 도와주는 작은 디렉터리입니다. 이 서버는 메시지 내용을 절대 볼 수 없으며, 한 번의 명령으로 제거할 수 있습니다.
실제로 무엇으로부터 보호되는지와 그 방법은 docs/THREAT_MODEL.md 및 docs/SECURITY.md를 참조하세요.
첫 실행 — 이름을 정하세요. 설정할 것은 그것뿐입니다.
대화 — 그룹 레일, 연락처 목록, 종단 간 암호화된 채팅 창.
설정 — 마이크 감도, 푸시 투 토크, 로그인 시 실행, 네트워크 연결 가능 여부.
crates/directory-server)는 사용자 ID를 현재 네트워크 주소에 매핑할 뿐 그 이상은 하지 않습니다. 구조적으로 메시지 내용을 읽을 수 없습니다. Cargo.toml은 그 방법을 아는 크레이트조차 의존하지 않습니다.이 앱에는 두 종류의 신원이 있으며, 의도적으로 분리되어 있습니다:
identity::Identity). 공개 "사용자 ID"는 그 키의 지문일 뿐입니다(wire_proto::user_id_from_ed25519). 생성에 서버가 관여하지 않기 때문에 어떤 서버도 이를 발급하거나 취소할 수 없습니다.PeerId)입니다. 재시작 후에도 변경될 수 있으며 채팅 신원에는 전혀 영향을 주지 않습니다. 둘은 사용자가 직접 서명한 프레즌스 레코드로만 묶여 있습니다.누군가를 찾는 것과 실제로 대화하는 것은 서로 다른 두 단계입니다:``` ┌────────────────────────┐ │ directory server │ │ (axum + one SQLite │ │ file: users, │ │ presence, group │ │ rosters. Never │ │ message content.) │ └─────────┬───────────────┘ 1. "where is bob │ 2. "here's my current right now?" │ address" (signed, │ expires in minutes) ┌─────────┴───────────────┐ ▼ ▼ ┌───────┐ 3. direct libp2p ┌───────┐ │ alice │◄──── connection ────►│ bob │ └───────┘ (Noise + Olm/ └───────┘ Megolm encrypted)
1. Alice는 디렉터리에서 Bob의 사용자 ID로 그를 찾는다. 그러면 Bob의
공개 키와 마지막으로 알린 네트워크 주소가 반환된다. 디렉터리가
보관하는 것은 그것이 전부다: 공개 키, 표시 이름, 그룹
멤버십 목록, 그리고 수명이 짧은 주소 알림
(`crates/directory-server`).
2. Alice는 libp2p(QUIC 또는 TCP+Noise, NAT 뒤에 있는 피어를 위한 릴레이 +
홀 펀칭 지원; `crates/net` 참조)를 통해 Bob에게 직접 연결한다.
디렉터리는 이 시점부터 완전히 관여하지 않는다.
3. 실제 메시지는 libp2p 연결에 올려지기 전에 1:1 채팅용 **Olm** 또는
그룹용 **Megolm**(`crates/crypto-session`)으로 암호화된다. 이는
모든 메시지가 고유한 키를 갖는 Double-Ratchet 방식의 기법이다.
서버 측 받은편지함은 없다: Bob이 오프라인이면 메시지는 로컬에서
대기하고 재시도되며, 다른 누군가의 인프라에 저장되지 않는다.
위의 모든 것은 Tauri 앱의 Rust 백엔드(`apps/desktop/src-tauri`)가 실제로
호출하는 `crates/core`의 `AppService`에 의해 조율된다. UI는
네트워크와 직접 통신하지 않는다.
## 프로젝트 구조```
crates/
wire-proto shared signed-request types for the directory API
identity vodozemac identity, OS-keychain key management
storage local encrypted store (contacts, messages, groups)
net libp2p transport + directory HTTP client
crypto-session Olm (1:1) / Megolm (group) session management
core orchestrates the above into `AppService` / `ChatNode`
directory-server the one server component (axum + SQLite)
apps/desktop the Tauri + React app
scripts/ build + backend-deployment scripts (§2, §5)
모든 플랫폼에서 Rust와 Node.js가 필요하며, Tauri가 네이티브 창을 빌드하는 데 필요한
플랫폼별 툴체인도 필요합니다. storage와 directory-server는 또한 SQLite를 소스에서
번들 컴파일하므로 일반 C 컴파일러가 필요합니다(이 프로젝트의 어디에서도
OpenSSL이나 다른 네이티브 암호화 라이브러리는 필요하지 않습니다).
모든 플랫폼 공통:
C 컴파일러, pkg-config, 그리고 Tauri의 Linux 백엔드가 링크하는 WebKitGTK/AppIndicator 개발 패키지를 설치합니다.
Debian/Ubuntu:```sh
sudo apt update
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file
libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev pkg-config
Fedora:```sh
sudo dnf install webkit2gtk4.1-devel openssl-devel curl wget file \
libappindicator-gtk3-devel librsvg2-devel pkgconf-pkg-config
sudo dnf group install "C Development Tools and Libraries"
아키텍처:```sh
sudo pacman -S --needed webkit2gtk-4.1 base-devel curl wget file openssl
appmenu-gtk-module libappindicator-gtk3 librsvg pkgconf
(Tauri 릴리스마다 패키지 이름이 달라질 수 있습니다. 빌드 중 누락된 `.pc` 파일을 찾지 못해 실패한다면, 사용 중인 배포판의 [현재 Tauri Linux 사전 요구 사항](https://v2.tauri.app/start/prerequisites/)을 확인하세요.)
</details>
<details>
<summary><strong>Windows</strong></summary>
1. **Microsoft C++ Build Tools**를 설치합니다(Visual Studio 설치 관리자 →
"Desktop development with C++" 워크로드). Tauri의 네이티브 셸과 번들된 SQLite를 컴파일하는 데 모두 필요합니다.
2. **MSVC** Rust 툴체인을 설치합니다: `rustup default stable-msvc`.
3. **WebView2**: Windows 11 및 대부분의 최신 Windows 10 설치에는 이미 포함되어 있습니다. 없는 경우, Tauri 빌드에서 Evergreen 런타임 설치를 안내합니다.
</details>
---
## 2. 빌드
저장소 루트에서:```sh
# Rust workspace (backend crates + the directory server)
cargo build --workspace --release
# Frontend + the actual desktop app bundle (installer/.app/.exe)
cd apps/desktop
npm install
npm run tauri build
npm run tauri build 는 저장소 루트의 아래에 플랫폼 네이티브 설치 프로그램을 생성합니다 (이것은 Cargo 워크스페이스이므로 Tauri 앱을 포함한 모든 크레이트가 하나의 최상위 디렉터리를 공유합니다).
크로스 컴파일(예: macOS에서 Windows 설치 프로그램 빌드)은 설정되어 있지 않습니다: 각 대상 플랫폼에서 빌드하거나, CI 기반 릴리스를 원하면 Tauri의 GitHub Actions 워크플로우를 사용하세요.
target/release/bundle/target/scripts/ 에는 플랫폼/출력별 빌드 스크립트가 하나씩 있으며, 각각 독립적으로 실행할 수 있고 실제로 작동하는 아티팩트를 생성하는 것이 검증되었습니다:
| Script | 생성물 |
|---|---|
scripts/build-mac-dmg.sh | macOS .dmg 설치 프로그램 |
scripts/build-mac-app.sh | 원시 macOS .app 번들, 설치 프로그램 없음 |
scripts/build-linux.sh | Linux .AppImage + .deb |
scripts/build-windows.ps1 | Windows .msi + .exe (NSIS) |
각 스크립트는 적절한 플래그와 플랫폼 검사를 사용하여 npm run tauri build --bundles <...> 를 감쌀 뿐입니다. 다른 번들 조합을 원하면 직접 원시 명령을 실행하세요 (apps/desktop 에서 npx tauri build --help).
scripts/release.sh vX.Y.Z 는 필요한 모든 위치의 버전을 올리고 커밋에 태그를 답니다 — docs/RELEASING.md 참조. macOS와 Linux에서 실행되며, 커밋하거나 푸시하지 않습니다.
서버 선택 화면(§3)에는 항상 세 가지 옵션이 표시됩니다: Seal (자체 공식 네트워크), Custom server, 그리고 하단의 작은 Local test server 링크. "Seal"은 빌드 시점에 URL을 구워 넣기 전까지 비활성화되어 있습니다 (회색으로 표시되며 "이 빌드에는 아직 설정되지 않음" 표시):```sh SEAL_DEFAULT_DIRECTORY_URL=https://directory.example.com npm run tauri build
자체 서버를 구축하고(§5) 실제 도메인을 가리키도록 설정했다면, 이 값을 설정하고 다시 빌드하세요. 그러면 이후 배포하는 모든 복사본에는 다른 코드를 건드리지 않아도 "Seal"이 해당 URL을 사용하는 실제 선택 가능한 옵션으로 표시됩니다. 일반/개발 빌드에서는 설정하지 않은 채로 두세요. 이 저장소에서는 공식 서버를 호스팅하지 않으므로 "Seal"은 비활성화 상태로 유지되며, 앱이 실제로 아무것도 실행하지 않는 플레이스홀더 도메인을 조용히 가리키기보다는 사용자가 커스텀 서버나 로컬 서버를 사용하게 됩니다.
---
## 3. 개발 모드에서 실행하기```sh
cd apps/desktop
npm install
npm run tauri dev
이 명령은 Vite 개발 서버를 시작하고, Rust 백엔드를 디버그 모드로 컴파일하며, 프런트엔드에 핫 리로드가 적용된 네이티브 창을 엽니다. 첫 빌드는 전체 의존성 트리를 컴파일하므로 몇 분이 걸리지만, 이후 실행은 빠릅니다.
처음 실행할 때 Seal은 어떤 디렉터리 서버를 사용할지 다음 순서로 묻습니다:
127.0.0.1:47100/47101에 바인딩되고, 데이터는 OS의 앱 데이터 디렉터리에 저장됩니다.
Seal을 시험해 보거나 한 대의 머신에서 인스턴스를 테스트하기에는 좋지만,
실제 배포는 아닙니다. 두 번째 인스턴스가 해당 포트가 이미 사용 중임을 발견하면
새 서버를 시작하는 대신 첫 번째 인스턴스의 서버를 재사용합니다.
그 덕분에 한 대의 머신에서 두 인스턴스가 서로를 찾을 수 있습니다. 이 항목은
"Seal"이 구성되어 있지 않고 다른 것을 선택하지 않으면
자동으로 선택됩니다.선택 사항은 저장되고(server.json이 앱의 다른 로컬 데이터 옆에 위치),
이후 모든 실행에서 조용히 재사용됩니다. 설정 → 디렉터리
서버에서 변경할 수 있으며, 변경 사항은 실행 중인 연결을 핫스왑하려 하지
않고 다음에 앱을 시작할 때 적용됩니다. 스크립트/개발 용도로는
환경 변수가 프롬프트를 완전히 건너뜁니다:```sh
P2P_CHAT_DIRECTORY_URL=https://directory.example.com npm run tauri dev
### 로컬에서 두 인스턴스 실행하기 (실제 메시징 테스트용)
각 인스턴스는 고유한 정체성이 필요합니다. Seal은 여러 계정을 기본적으로 지원하지만(이 기기의 설정 → 계정), 한 머신에서 두 개의 *별도 프로세스*를 실행하려면 `P2P_CHAT_PROFILE`이 더 빠른 방법입니다. 이 변수는 해당 이름의 계정을 비대화형으로 (첫 실행 시) 자동 생성하거나 (이후 실행마다) 자동 재개하며, 계정 선택 화면을 완전히 건너뜁니다:```sh
# terminal 1
P2P_CHAT_PROFILE=alice npm run tauri dev
# terminal 2
P2P_CHAT_PROFILE=bob npm run tauri dev
서버 선택(server.json)과 계정 목록(accounts.json)은
모두 한 머신의 프로세스 간에 공유되며, 프로필별로 공유되지 않습니다. 맨
처음 실행하는 인스턴스가 서버를 선택하고, 이후의 모든 프로필은
(여기서는 bob 포함) 자동으로 그 서버를 재사용합니다. 두 창은 모두
동일한 임베디드 디렉터리 서버에 연결되므로, 서로를 ID로 연락처에 추가하고
두 창 사이에서 메시지를 주고받을 수 있습니다.
Vite의 dev 서버는 Tauri의 webview가 가리킬 실제 고정 포트가 필요하며,
일반적으로는 npm run tauri dev를 한 번에 하나만 실행할 수 있습니다 —
두 번째 실행은 포트 1420이 이미 사용 중임을 발견하고 곧바로 실패합니다.
npm run tauri는 실제로는 작은 래퍼(apps/desktop/scripts/tauri.mjs)로,
첫 번째 이후의 모든 인스턴스에 대해 다음 사용 가능한 포트(1421, 1422, …)을 선택하고
이를 자동으로 연결해 주므로, 위의 두 명령을
두 개의 터미널에서 실행하면 그냥 작동합니다. 다르게 할 필요가 없습니다. 이는
dev에 대해서만 동작을 변경합니다 — npm run tauri build 및 그 외의 모든 것은
실제 CLI로 그대로 전달됩니다.
./scripts/run-two-mac-instances.sh # profiles: alice, bob ./scripts/run-two-mac-instances.sh carol dave
위와 같은 아이디어지만, 실제 빌드된 앱(`build-mac-app.sh` /
`build-mac-dmg.sh`의 출력물, 또는 `/Applications`에 설치된 복사본)을
`npm run tauri dev` 대신 서로 다른 `P2P_CHAT_PROFILE`로 두 번 실행하여
실제 사용자가 실행하는 환경에 더 가깝습니다. 두 프로세스의 PID와 종료 방법을 출력합니다.
### 디버깅
- **Rust 로그**: 실행 전에 `RUST_LOG`를 설정하세요. 예:
`RUST_LOG=debug npm run tauri dev` (또는 `RUST_LOG=p2p_core=debug,net=debug`로
범위를 좁힐 수 있습니다). 기록되는 필드는 메타데이터(피어/그룹/사용자
ID, 오류 유형)로 제한됩니다. 자세한 로그를 남겨도 안전한 이유는
[`docs/SECURITY.md`](https://github.com/emn4tor/seal/blob/HEAD/docs/SECURITY.md)를 참조하세요.
- **프론트엔드**: 개발 창은 실제 웹뷰입니다. 마우스 오른쪽 버튼 클릭 → 요소 검사
(또는 개발자 도구 열기)는 일반 브라우저처럼 동작합니다.
- **백엔드 크레이트를 개별적으로**: 각 크레이트에는 자체 테스트 스위트가 있어 UI를 전혀 건드리지 않고
실행하고 반복할 수 있습니다. §4를 참조하세요.
- **독립형 디렉터리 서버**(내장 서버 대신): §5를 참조하세요.
---
## 4. 테스트```sh
# everything
cargo test --workspace
# one crate, e.g. the full backend-to-backend flow a Tauri command would trigger
cargo test -p p2p-core --test app_service
# lint + format check (what CI runs)
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
# dependency vulnerability scan
cargo install cargo-audit --locked # once
cargo audit
# frontend type-check + build
cd apps/desktop && npm run build
이것이 실제로 무엇인지 정리하자면, 과장해서 상상하기 쉬우니: 하나의 axum
프로세스, 하나의 SQLite 파일, 세 종류의 레코드(공개 키, 수명이 짧은
프레즌스 알림, 그룹 명단), 모든 쓰기는 호출자 자신의
신원 키로 서명됩니다. 이 서버는 메시지 경로에 절대 포함되지 않습니다. 그 이유는
docs/THREAT_MODEL.md에서 확인하세요.
구조적으로도, 단순한 정책만이 아닙니다: directory-server의 Cargo.toml은
메시지 내용을 읽는 방법을 아는 크레이트에조차 의존하지 않습니다.
sudo ./scripts/setup-backend.sh
대화형이며 Linux + systemd 전용입니다 (이유는 스크립트 헤더 참조). 스크립트는
사용 중인 배포판 계열을 묻습니다 (Debian/Ubuntu, Fedora/RHEL/Rocky/Alma,
Arch/Manjaro 또는 openSUSE. `/etc/os-release`에서 추측한 값으로 미리 채워져
있어서 보통 한 번의 키 입력으로 확인할 수 있습니다). 그리고 각 계열별 전용 함수로
해당 배포판의 빌드 전제 조건을 설치하고, Rust가 없으면 `rustup`을 통해
설치할 것을 제안하며, 릴리스 바이너리를 빌드하고, 전용 시스템 사용자를
만듭니다. 관리자 토큰을 생성하고, 자동 HTTPS와 함께 도메인을
[Caddy](https://caddyserver.com)로 구성할지 묻습니다 (배포판별로 Caddy 자체를
설치하며, 배포판 패키지가 없으면 Caddy 공식 정적 바이너리로 대체합니다).
아니면 직접 앞단을 구성하고 싶다면 루프백/일반 HTTP에만
바인딩할지 묻습니다. 그런 다음 systemd 서비스를 작성하고
활성화합니다. 다시 실행해도 안전합니다.
아래 내용은 실제로 수행하는 작업입니다. 직접 실행하거나 실행 전에 이해하고 싶다면
참고하세요.
### macOS: 빠른 LAN 테스트 서버```sh
./scripts/run-mac-test-server.sh
실제 호스팅용이 아닙니다. 도메인, TLS 또는 systemd(macOS에는 어차피 존재하지 않음)를
설정하지 않고도 동일한 네트워크의 두 기기(예: Mac + 다른 기기, 또는 같은 Wi-Fi를
사용하는 두 사람)에서 앱을 테스트하기 위한 것입니다. 릴리스 바이너리를 빌드하고,
관리자 토큰을 생성하며(이후 실행에서 재사용됨), 공개 API를 모든 인터페이스에
바인딩하고, 사용할 URL을 출력합니다.
Mac의 실제 LAN IP(ipconfig getifaddr)를 사용하므로, 127.0.0.1만이 아니라
다른 기기에서도 접근할 수 있습니다. 관리자 포트는 루프백에서만 유지됩니다.
포그라운드에서 실행되며, Ctrl-C로 중지합니다. 데이터는
~/.seal-test-server 아래에 저장됩니다.
DIRECTORY_DB_PATH=/var/lib/seal-directory/directory.sqlite3
DIRECTORY_PUBLIC_ADDR=0.0.0.0:8080
DIRECTORY_ADMIN_ADDR=127.0.0.1:8090
DIRECTORY_ADMIN_TOKEN=$(openssl rand -hex 32)
cargo run --release -p directory-server --bin directory-server
| 변수 | 필수 | 의미 |
|---|---|---|
| `DIRECTORY_DB_PATH` | 아니요 (기본값 `directory.sqlite3`, 현재 작업 디렉터리) | 단일 SQLite 파일이 위치하는 곳입니다. 상위 디렉터리가 존재해야 합니다. |
| `DIRECTORY_PUBLIC_ADDR` | 아니요 (기본값 `0.0.0.0:8080`) | 앱이 통신하는 rendezvous API입니다. 공개적으로 노출해도 됩니다. |
| `DIRECTORY_ADMIN_ADDR` | 아니요 (기본값 `127.0.0.1:8090`) | purge 엔드포인트입니다. 공개 인터넷에 노출하지 마세요. 아래를 참조하세요. |
| `DIRECTORY_ADMIN_TOKEN` | **예** | 관리자 API용 Bearer 토큰입니다. 이 값이 없으면 프로세스가 시작되지 않습니다. `openssl rand -hex 32` 또는 유사한 방법으로 생성하고, 다른 곳에서 재사용하지 마세요. |
프로세스는 시작 시 바인딩한 주소를 로그로 남기고, 만약
`DIRECTORY_ADMIN_ADDR`이 루프백이 아니라면 크게 경고합니다.
### 앱에서 이 서버를 가리키기
일반적으로 사용하게 되는 순서대로 세 가지 방법이 있습니다:
1. **최초 실행 화면**: "Custom server"를 선택하고 URL을 입력합니다. §3을 참조하세요.
2. **설정 → 디렉터리 서버**: 나중에 변경할 수 있으며, 다음
재시작 시 적용됩니다.
3. **`P2P_CHAT_DIRECTORY_URL`**, 실행 전에 설정: 묻는 과정을 완전히 건너뛰고
저장된 값을 모두 덮어쓰며, 개발/스크립트 실행에 유용합니다: ```sh
P2P_CHAT_DIRECTORY_URL=https://directory.example.com npm run tauri dev
Everyone who wants to find each other needs to point at the same directory instance; it's how they look each other up in the first place.
[Service] Type=simple User=seal-directory Group=seal-directory Environment=DIRECTORY_DB_PATH=/var/lib/seal-directory/directory.sqlite3 Environment=DIRECTORY_PUBLIC_ADDR=127.0.0.1:8080 Environment=DIRECTORY_ADMIN_ADDR=127.0.0.1:8090 EnvironmentFile=/etc/seal-directory/admin-token.env ; DIRECTORY_ADMIN_TOKEN=... ExecStart=/usr/local/bin/directory-server Restart=on-failure
ProtectSystem=strict ProtectHome=true PrivateTmp=true NoNewPrivileges=true ReadWritePaths=/var/lib/seal-directory
[Install] WantedBy=multi-user.target
참고:
- `DIRECTORY_PUBLIC_ADDR`는 여기서는 의도적으로 **루프백**에 바인딩됩니다. TLS를 위해
axum을 인터넷에 직접 노출하는 대신 아래와 같이 리버스 프록시를 앞에
두세요.
- `seal-directory` 시스템 사용자/그룹을 생성하고,
`/var/lib/seal-directory`를 먼저 생성하세요 (`useradd --system --no-create-home
seal-directory && install -d -o seal-directory -g seal-directory
/var/lib/seal-directory`), 그리고 빌드된 `directory-server` 바이너리를
(`target/release/`에서) `/usr/local/bin/`으로 복사하세요.
- 관리자 토큰은 root만 읽을 수 있는 `EnvironmentFile`에 넣고, 직접
유닛 파일에 넣지 마세요 (유닛 파일은 종종 모든 사용자가 읽을 수 있습니다).
</details>
### 리버스 프록시를 통한 TLS
<details>
<summary>Caddy / nginx 구성 보기</summary>
[Caddy](https://caddyserver.com)는 가장 적은 설정으로 자동 HTTPS를 제공합니다.
설정:```
# /etc/caddy/Caddyfile
directory.example.com {
reverse_proxy 127.0.0.1:8080
}
caddy run (또는 systemctl enable --now caddy)은 인증서 발급/갱신을
자체적으로 처리합니다. nginx를 사용하고 싶다면 TLS를 nginx에서 종료하고
proxy_pass http://127.0.0.1:8080;를 사용하세요. 앱은 프록시 관점에서
일반 HTTP만 필요하기 때문입니다.
방화벽 측면에서: 외부에서 접근 가능해야 하는 것은 공용 포트뿐입니다
(위 예시의 8080, 프록시를 통해 443이 앞에 있음). 관리자 포트는
절대 외부에서 접근 가능하면 안 됩니다. SSH 포트 포워딩
(ssh -L 8090:127.0.0.1:8090 your-server)을 사용해 원격으로 purge를 실행해야 할 때
접근하세요.
cargo run --release -p directory-server --bin directory-admin --
--admin-url http://127.0.0.1:8090 --token "$DIRECTORY_ADMIN_TOKEN" purge
이것은 SQLite 파일을 삭제하고 빈 스키마를 다시 생성합니다. `DELETE` 문도 없고, 부분적인 상태도 없습니다. 먼저 누구에게 알리지 않고 실행해도 안전합니다. 그 안의 모든 레코드는 각 클라이언트가 이미 로컬에 보유한 데이터(자체 등록 정보, 현재 상태, 자신이 속한 그룹 명단)의 캐시일 뿐이므로, 클라이언트는 다음 작업을 수행하는 순간 몇 분 안에 다시 채웁니다. 이 데이터베이스에는 의도적으로 백업 정책이 없습니다. 백업을 유지하는 것이 전체 목적을 훼손하는 이유는 [`docs/SECURITY.md`](https://github.com/emn4tor/seal/blob/HEAD/docs/SECURITY.md)를 참조하세요.
---
## 6. 앱 사용하기
1. **첫 실행, 첫 질문**: 사용할 디렉터리 서버를 선택합니다(§3). 기본값은 실행 중인 빌드에 내장된 값입니다(그것을 빌드한 사람이 공식 서버를 구성하지 않았다면 로컬 테스트 서버). 직접 또는 신뢰하는 누군가가 호스팅하는 서버를 지정하려면 "사용자 지정 서버"를 선택하세요.
2. **표시 이름을 선택합니다.** 이렇게 하면 기기에 개인 키 쌍이 생성되고(기억할 것이 없으며, 분실 시 복구할 수도 없습니다. 의도적인 설계입니다) 암호화가 실제로 어떻게 작동하는지에 대한 짧은 인앱 설명이 진행됩니다. 언제든지 설정에서 다시 볼 수 있습니다. 이후 모든 실행에서는 프롬프트 없이 바로 다시 들어갑니다. 이 작업은 계정당 한 번만 발생합니다. 이 기기의 설정 → 계정에서 추가 계정(완전히 분리된 ID)을 추가하고 다시 시작하지 않고 전환할 수 있습니다.
3. **사람 추가**: "다이렉트 메시지" 옆의 **+**를 클릭하고 해당 ID(상대방의 설정 → 내 ID에서 찾을 수 있음)를 입력합니다. 설계상 탐색할 디렉터리가 없습니다. 전화번호를 공유하는 것과 같은 방식으로 연결합니다.
4. **메시지 보내기**: 목록에서 이름을 선택하고 입력합니다. 누군가에게 보내는 첫 메시지는 자동으로 암호화된 세션을 설정합니다.
5. **그룹 시작**: 아이콘 레일에서 **+**를 클릭하고 이름을 지정한 다음 같은 방법으로 ID로 사람들을 초대합니다. 누군가를 제거하면 그룹의 키가 회전되어 이후에 전송된 내용을 읽을 수 없습니다.
6. **모두 삭제**: 설정 → 데이터 및 개인정보 보호. 즉시, 로컬 전용이며 되돌릴 수 없습니다. *이 기기*의 키, 연락처 및 기록을 파괴하며 대화한 상대방에게는 아무 영향도 미치지 않습니다.