
Tailscale을 통한 Linux 워크스테이션용 자체 호스팅 컨트롤 데크 및 원격 데스크탑으로, WebRTC 스트리밍, 로컬 LLM을 이용한 음성 제어, 씬 매크로, 가상 입력, 파일 전송, 실시간 시스템 텔레메트리 기능을 제공합니다.
여분의 안드로이드 폰(또는 브라우저가 있는 모든 기기)을 개인 Tailscale 네트워크를 통해 Linux 워크스테이션용 자체 호스팅 컨트롤 데크 및 원격 데스크톱으로 전환합니다 — 타사 앱 없이.
"낡은 폰으로 뭘 하지?"에서 시작해 Hyprland 장비의 컨트롤 플레인이 되었습니다: 창/작업 공간 제어, 씬 매크로, 로컬 LLM 어시스턴트가 있는 푸시투토크 음성 라우터, 컨텍스트 인식 인앱 컨트롤, 양방향 오디오 + 화면 스트리밍, 터치 원격 데스크톱, 가상 키보드/마우스, 파일 전송 + 폰→PC 공유, 실시간 텔레메트리 — 이 모든 것을 설치형 PWA로 제공하며, 포스퍼 터미널 느낌을 냅니다.
이식성에 대한 참고사항. 이 프로젝트는 하나의 특정 설정을 위해 만들어졌습니다: Arch 기반 Linux (Garuda) + Hyprland + PipeWire + NVIDIA + Tailscale, 그리고 end-4 / illogical-impulse 도트 파일. 아키텍처는 일반적이지만, 여러 통합 요소는 환경에 따라 다릅니다 (모니터 이름, 작업 공간 바인딩, matugen 테마 경로,
cpupower/nvidia-smi). 이것을 바로 사용할 수 있는 패키지가 아니라, 적응을 위한 작업 참조로 취급하세요.
UI는 탭 방식의 어둡고 가로형인 웹 앱입니다 (테마는 데스크톱과 동기화됨):
| 탭 | 기능 |
|---|---|
| 작업 공간 | 모니터별 실시간 작업 공간 그리드 (탭하여 전환), 실시간 창 목록 (탭하여 포커스), 모니터별 DPMS 토글 + ddcutil 밝기 |
| 원격 | 화면 터치패드 (드래그/탭/두손가락) + 수정자 및 조합키를 지원하는 가상 키보드, 커널 uinput 장치를 통해 |
| 모드 | 한 번 탭으로 씬 매크로 — 여러 모니터에 걸쳐 전체 앱 레이아웃 실행/닫기/배치 (예: "작업" / "자유") |
| 음성 | 푸시투토크 음성 라우터 (faster-whisper, CPU): 선두 단어 레인 — macro (모드/명령 실행), type (받아쓰기), input (구문 → 키 입력) — 실행 전 확인 포함, 추가로 "friday" (읽기 전용 로컬 LLM 응답기, Ollama + Qwen)와 자체 호스팅 웹 검색 및 실시간 시스템 상태 인식 |
| 오디오 | 마이크/스피커 음소거, 볼륨, 출력 및 입력 장치 선택기, playerctl 전송 + 커버 아트 (활성 MPRIS 플레이어 추적) |
| 스트림 | 양방향 WebRTC 오디오 (PC↔폰, 폰을 마이크로 사용 및 폰 전용 출력) + 화면 비디오, 그리고 스트리밍 화면 터치로 제어 = 실제 원격 데스크톱 |
| 시스템 | 성능 모드 전환, 실시간 테마 전환, Tailscale 상태, 상위 프로세스 (탭하여 종료), 잠금, NetworkManager 재시작, 절전/재부팅/전원 끄기 |
| 파일 | 모니터 스크린샷 → 폰에서 보기/다운로드; 폰→PC로 파일 드롭 |
| 설정 | commands.json / modes.json (및 컨텍스트/음성) 설정을 폰에서 편집 (JSON 유효성 검사) |
항상 탭 위에 표시되는 컨텍스트 스트립 — 현재 상황을 보여줍니다: 활성 통화 (음소거 / 바로 이동), 미디어 재생 중 + 전송, 그리고 포커스된 앱 (YouTube / Brave / Teams)을 위한 인앱 키보드 컨트롤 — 포커스를 빼앗지 않으면서 제공합니다.
탭 외에도 두 가지 기능이 더 있습니다:
추가로: 화면 숫자 키패드 PIN 로그인, 화면 웨이크 락, 전체화면 가로 PWA, 로그인 시 무차별 대입 잠금.
폰 / 노트북 (브라우저 PWA)
│ HTTPS + WSS (Tailscale 전용)
▼
tailscale serve ──► FastAPI 웹 앱 ── Unix 소켓 ──► deckd
(실제 TLS 인증서) (사용자 권한으로 실행) (액션 이름) (root 권한으로 실행)
│ │
hyprctl · pactl · ddcutil · grim · 고정 허용 목록의
wf-recorder · uinput · WebRTC 권한 있는 명령
(cpupower, nvidia-smi,
systemctl, …)
두 개의 프로세스:
app/ — 웹 앱은 일반 사용자 권한으로 실행됩니다. root가 필요하지 않은 모든 작업을 수행합니다: Hyprland 제어, 오디오, 밝기, 화면 캡처, 가상 입력, WebRTC 스트리밍, 파일 전송.deckd/ — 작은 root 도우미 (표준 라이브러리만 사용, 의존성 없음) — 소수의 권한 있는 작업을 위해. 웹 앱은 셸 문자열을 보내지 않으며, 고정 허용 목록의 액션 이름만 보냅니다 (governor_performance, gpu_power_limit, suspend, …). 이는 deckd/actions.py에서 실행 전에 검증됩니다. 웹 앱이 완전히 손상되어도 피해 범위는 인수 주입 없이 허용 목록에 있는 액션으로 제한됩니다. 소켓은 root:<그룹> 모드 0660입니다.tailscale serve가 MagicDNS 이름으로 실제 Let's Encrypt 인증서와 함께 노출하며, 절대 0.0.0.0에 바인딩하지 않습니다.웹 앱은 설정된 셸 명령을 실행하고 사용자 권한으로 입력을 주입할 수 있습니다 — 설계상으로는 컴퓨터의 원격 제어입니다. Tailnet 내에서 유지하고 PIN 뒤에 두며, 공개적으로 노출하지 마세요.
pactl/PulseAudio 호환 포함)hyprctl, pactl / pw-record / pw-play, playerctl, ddcutil,
wf-recorder, grim, cpupower, nvidia-smi, kitty (또는 다른 터미널)input 그룹 (/dev/uinput용) 및 그룹 (용)에 속해야 함git clone <your-repo-url> phone-deck
cd phone-deck
uv sync # .venv 생성 및 의존성 설치
uv run python -m app.set_pin # 잠금 해제 PIN 설정
deckd)먼저 systemd/deckd.service를 편집하세요 — 경로와 DECK_SOCKET_GROUP을 사용자가 속한 그룹(보통 기본 그룹)으로 설정하세요:
sudo cp systemd/deckd.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now deckd
deckd는 시스템 Python을 사용합니다 (표준 라이브러리만 — venv 불필요).
개발 중에는 직접 실행:
uv run uvicorn app.main:app --host 127.0.0.1 --port 8765
… 또는 사용자 서비스로. 먼저 systemd/phone-deck.service 경로를 편집한 후 설치하세요.
중요: Hyprland가 uwsm을 통해 시작되지 않는 설정에서는 systemd graphical-session.target이 활성화되지 않으므로, 사용자 유닛이 로그인 시 자동 시작되지 않습니다. 안정적인 해결책은 세션 환경을 가져와 Hyprland에서 시작하는 것입니다 — Hyprland 자동 시작에 추가하세요 (예: end-4의 ~/.config/hypr/custom/execs.conf):
exec-once = systemctl --user import-environment WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS XDG_CURRENT_DESKTOP && systemctl --user start phone-deck
인증서를 한 번 활성화: Tailscale 관리 콘솔 → DNS → Enable HTTPS Certificates.
실제 인증서로 앱을 제공:
sudo tailscale serve --bg 127.0.0.1:8765
HTTPS가 활성화되면 웹 앱 환경에 DECK_SECURE_COOKIES=1을 설정하세요.
폰에서 Chrome을 열어 https://<your-host>.<your-tailnet>.ts.net/에 접속하세요 (Tailnet에 있어야 함), 로그인 후 ⋮ → Install app. 전체 화면, 가로 고정으로 실행되며 화면이 켜진 상태를 유지합니다. PWA 서비스 워커 + 웨이크 락을 위해 HTTPS가 필요합니다.
모든 Tailnet 기기에서 작동합니다 — 노트북 포함 (터치 표면은 Pointer Events를 사용하므로 마우스도 작동). WebRTC 스트림은 단일 피어입니다 (한 번에 한 명의 뷰어).
모든 런타임 설정은 ~/.config/phone-deck/에 있으며 설정 탭에서 편집 가능합니다 (JSON 유효성 검사, .bak 유지). 변경 사항은 다음 작업 시 적용됩니다 — 재시작 불필요.
commands.json — 사용자 정의 셸 액션[
{ "id": "reload_wm", "label": "Reload WM", "run": "hyprctl reload" },
{ "id": "deploy", "label": "Deploy site", "run": "cd /srv/app && ./deploy.sh",
"confirm": true, "timeout": 120 }
]
run은 문자열 (bash -lc로 실행) 또는 목록(argv, 셸 없음)일 수 있습니다. 사용자 권한으로 실행됩니다 — 로컬 신뢰 파일입니다.
modes.json — 씬 매크로각 모드는 hyprctl을 통해 실행되는 단계의 순서 목록입니다:
launch {cmd, workspace, match?, once?} — 작업 공간에서 앱을 엽니다. match (창 클래스) + once가 있으면 중복되지 않고, 잘못된 작업 공간에 위치할 경우 창을 이동합니다 (브라우저 PWA처럼 기존 프로세스에서 창이 오는 앱에 필요).close {match, workspaces?} — 클래스의 창을 닫습니다, 선택적으로 나열된 작업 공간에서만.move {match, workspace} — 일치하는 창을 작업 공간으로 이동합니다.focus {workspace} — 작업 공간을 화면에 표시합니다 (예: 각 모니터를 홈 작업 공간으로 재설정).모드 배치는 작업 공간이 모니터에 고정된 경우에만 결정적입니다. Hyprland 설정에 바인딩을 추가하세요 (작업 공간 번호와 모니터 이름을 사용자 환경에 맞게 조정):
workspace = 1, monitor:DP-3, default:true
workspace = 2, monitor:DP-3
# … 등
데크는 포스퍼 터미널 스킨을 입고 있으며, 네 가지 실시간 전환 가능한 색상 프로필이 있습니다 — 녹색 (기본값), 호박색, 아이스, 자동 (matugen 배경화면 팔레트에서 포스퍼 색조를 가져옴, ~/.local/state/quickshell/user/generated/colors.json). 시스템 탭에서 전환하며, 선택은 ~/.config/phone-deck/theme.json에 유지됩니다. app/theme.py가 /theme.css로 제공합니다. Ambient Cogitator의 유휴 시간 초과와 리터지 라인도 여기서 설정 가능합니다.
aiortc + PyAV를 통한 양방향 WebRTC, 서브프로세스 파이프를 통해 PipeWire/Hyprland와 연결 (취약한 장치 I/O 바인딩 회피):
parec로 기본 싱크의 .monitor를 캡처 → Opus.wf-recorder가 모니터를 캡처 → aiortc, 품질 프리셋 (540p / 720p / 1080p)으로 불안정한 링크에서도 비트레이트를 적절히 유지.hyprctl movecursor) + uinput 클릭. 탭 = 클릭, 드래그 = 이동, 길게 누르기 = 오른쪽 클릭.제약: ~150–300 ms 지연; PWA가 포그라운드일 때만 안정적 (안드로이드는 백그라운드 탭을 일시 중단); 에코 방지를 위해 헤드폰 사용; 한 번에 한 스트리밍 피어; 브라우저는 폰 자체 앱 오디오를 캡처할 수 없음 (마이크만).
tailscale serve로 HTTPS/WSS)uv run pytest
deckd 허용 목록, JWT + 로그인 잠금, PIN 해싱, 설정 편집기 검증, 모드 파싱, 스트리밍 품질/명령 로직, 입력 키맵을 다룹니다. 실시간 WebRTC/PipeWire/Hyprland 경로는 개발 중 루프백으로 검증됩니다.
app/ FastAPI 웹 앱
main.py 라우트 + WebSocket
hypr.py Hyprland (스냅샷, 디스패치, 이벤트 소켓, 커서)
audio.py PipeWire 장치 제어 + playerctl
audio_rtc.py WebRTC 오디오 + 화면 비디오 브리지
hid.py 가상 키보드/마우스 via uinput
modes.py 씬 매크로 엔진
brightness.py ddcutil
grab.py 스크린샷 + 파일 업로드
theme.py matugen → CSS
...
deckd/ root 도우미 (표준 라이브러리만) + 액션 허용 목록
systemd/ 서비스 유닛
tests/ pytest 스위트
MIT — LICENSE 참조 (다른 라이선스를 원하면 게시 전에 추가하세요).
Hyprland, PipeWire, Tailscale, aiortc, 그리고 end-4 dotfiles를 위해 제작되었습니다.
i2cddcutilaiortc + PyAV (uv를 통해 설치)