russh)·내장 터미널 에뮬레이터(vt100)·파이프라인 SFTP·SSH/시리얼/텔넷 세 종류 세션을 한 화면에 담았습니다. 화려한 기능보다 "네이티브 데스크톱 앱을 어떻게 가볍고 안전하게 만드나"가 배울 거리입니다. (jeff141/meatshell · Rust 2021 · Slint · MIT OR Apache-2.0 · v0.4.6 · ★692)서버 관리자나 개발자가 원격 서버에 접속할 때 쓰는 도구를 SSH 클라이언트라고 합니다. 그중 FinalShell은 "접속하면 왼쪽에 그 서버의 CPU·메모리 그래프가 같이 뜨고, 여러 서버를 탭으로 관리하고, 파일도 끌어다 올리는" 올인원 경험으로 인기가 많습니다. 문제는 FinalShell이 자바(Java)로 만들어져, 창 하나 띄우는 데 메모리를 400MB 넘게 먹는다는 점입니다.
meatshell은 바로 그 경험을 Rust로 처음부터 다시 짜서, 같은 기능을 수십 MB 메모리로 돌리려는 프로젝트입니다. 자바 가상머신(JVM) 같은 무거운 런타임 없이 네이티브 실행파일 하나로 떨어지고, UI도 웹브라우저(Electron)를 끼지 않고 Slint라는 순수 Rust GUI 툴킷으로 그립니다.
FinalShell은 편하지만, 시동을 걸 때마다 거대한 엔진(JVM)을 통째로 데우는 SUV 같습니다. 짐칸(기능)은 좋은데 연비(메모리)가 나쁩니다.
meatshell은 같은 짐칸을 그대로 두고 엔진만 통째로 갈아낸 경량차입니다. 자원 모니터·멀티탭·SFTP라는 짐은 똑같이 싣되, 시동(런타임) 부담을 수십 MB 수준으로 줄였습니다. 대가는 "아직 신생이라 짐칸 일부가 비어 있다"는 것 — 곧 채워 가는 중입니다.
.slint라는 전용 언어로 적으면, 빌드할 때 Rust 코드로 컴파일됩니다. 가비지 컬렉터(GC)가 없고 웹브라우저도 끼지 않아, Electron(브라우저를 통째로 넣는 방식)보다 훨씬 가볍습니다. meatshell이 "수십 MB"를 달성한 핵심 비결입니다."가벼운 네이티브 데스크톱 앱"은 2026년에도 여전히 갈증이 큰 분야입니다. 대부분의 데스크톱 앱이 Electron(앱마다 크롬 브라우저를 통째로 품는 방식)으로 만들어지면서, 메모장 하나가 수백 MB를 먹는 게 일상이 됐습니다. meatshell은 그 반대편에서 "같은 기능, 1/10 무게"를 실제로 보여 주는 표본이라 주목받습니다. 게다가 소재가 모두가 매일 쓰는 SSH 도구라, 공감대가 넓습니다.
| 항목 | FinalShell (JVM) | Electron 터미널 | meatshell (Rust+Slint) |
|---|---|---|---|
| 실행 런타임 | 자바 가상머신 | 크롬 + Node | 네이티브 (런타임 없음) |
| 대략 메모리 | 400MB+ | 200~500MB | 수십 MB 목표 |
| 자원 모니터 사이드바 | ○ (대표 기능) | 대개 ✕ | ○ (로컬 + 원격) |
| SFTP 파일 전송 | ○ | 일부 | ○ (파이프라인 가속) |
| 시리얼 / 텔넷 | 일부 | 드묾 | ○ (SSH와 한 파이프라인) |
| 비밀번호 저장 보안 | 약함(평문 논란 이력) | 제각각 | ChaCha20 암호화 저장 |
| 성숙도 | 매우 성숙 | 다양 | 성장 중 (v0.4대) |
meatshell의 UI는 Slint로 컴파일된 네이티브 코드입니다. HTML/CSS도, V8 엔진도, 가비지 컬렉터도 없습니다. 그래서 메모리·시작 속도에서 Electron·JVM 앱과 체급이 다릅니다. "Rust로 데스크톱 앱을 만들면 실제로 어떤 모습인가"를 보여 주는 살아있는 예제입니다.
원격 접속 방식은 SSH 말고도 시리얼 포트(라우터·임베디드 장비에 케이블로 직접)와 텔넷(구형 장비)이 있습니다. meatshell은 셋을 각각 따로 만들지 않고, "똑같이 생긴 통로(SessionHandle)"로 추상화해 터미널 화면 코드를 그대로 재사용합니다. 좋은 인터페이스 설계의 교본입니다.
SSH 클라이언트는 편의를 위해 서버 비밀번호를 저장해 두는데, 많은 도구가 이걸 평문이나 약한 인코딩으로 둡니다. meatshell은 ChaCha20-Poly1305로 암호화해 저장하고, 메모리에서 쓰고 난 비밀번호 버퍼는 zeroize로 즉시 0으로 덮습니다. 작은 앱인데도 보안 디테일을 챙긴 점이 인상적입니다.
meatshell은 현재 check_server_key가 어떤 서버 키든 무조건 통과시킵니다(StrictHostKeyChecking=no와 같음). 처음 붙는 서버가 진짜 그 서버인지 확인하지 않는다는 뜻이라, 중간자 공격(MITM)에 취약합니다. 개발 편의를 위한 임시 상태로, README도 "프로덕션 전 known_hosts 검증을 붙이라"고 명시합니다.
정식 SSH 클라이언트는 서버의 공개키 지문을 ~/.ssh/known_hosts에 저장해 두고, 다음 접속 때 지문이 바뀌면 경고·차단합니다. meatshell 로드맵의 v0.2 항목에도 들어 있는 기능으로, 이걸 직접 구현해 보는 게 좋은 실습이 됩니다(s8 참고).
meatshell은 100% Rust 데스크톱 앱입니다. 크게 네 덩어리로 나뉩니다 — 화면을 그리는 UI(Slint), 네트워크를 비동기로 굴리는 런타임(tokio), 실제 접속을 담당하는 전송 계층(russh·serialport), 그리고 비밀번호를 지키는 보안 계층(chacha20poly1305·zeroize)입니다. 외부 서버나 DB는 전혀 없습니다 — 모든 게 사용자 PC 안에서 끝납니다.
| 항목 | 내용 |
|---|---|
| 언어 | Rust edition 2021, 최소 버전(MSRV) 1.75 |
| UI 정의 | .slint 마크업 10개 (빌드 시 slint-build가 Rust로 컴파일) |
| 릴리스 최적화 | opt-level=3 · lto="thin" · codegen-units=1 · strip="symbols" · panic="abort" — 작고 빠른 바이너리를 위한 설정 |
| 배포 타깃 | Windows(x64) · Linux(x64·ARM64, glibc ≥ 2.35) · macOS(Intel·Apple Silicon) — Linux x64는 AppImage도 빌드 |
| 라이선스 | MIT OR Apache-2.0 (둘 중 택일하는 듀얼 라이선스) |
| 크레이트(라이브러리) | 역할 |
|---|---|
| slint / i-slint-backend-winit | 네이티브 GUI 렌더링. winit 백엔드는 OS 파일 드래그앤드롭을 직접 받기 위함 |
| tokio | 비동기 런타임 — 모든 네트워크 I/O를 굴리는 엔진 |
| russh / russh-keys / ssh-key | 순수 Rust SSH 구현 (libssh 같은 C 의존 없음). 비밀번호·개인키 인증 |
| russh-sftp | SFTP(파일 업로드/다운로드) 프로토콜 |
| vt100 | 터미널 에뮬레이션 — 서버가 보낸 ANSI 제어신호를 화면 격자로 해석 |
| serialport | 크로스플랫폼 시리얼 포트 (Linux는 libudev 필요) |
| sysinfo | 로컬 PC의 CPU/메모리/네트워크 지표 수집(사이드바) |
| chacha20poly1305 / zeroize | 비밀번호 암호화 저장 / 메모리 평문 즉시 소거 |
| arboard / rfd / directories | 클립보드 / 네이티브 파일 대화상자 / OS별 설정 폴더 경로 |
| tokio-socks / serde / tracing | SOCKS5 프록시 / JSON 직렬화 / 로깅 |
보통 SSH 앱은 C로 짠 libssh/OpenSSL을 끌어다 씁니다. 그러면 빌드가 까다롭고 플랫폼마다 깨지기 쉽습니다. meatshell은 SSH(russh)·암호화(chacha20poly1305)·터미널(vt100)까지 전부 순수 Rust 크레이트로 골라, 한 번의 cargo build로 4개 OS에 그대로 빌드되게 만들었습니다. 크로스플랫폼 배포가 쉬워지는 실전 선택입니다.
meatshell에서 가장 배울 점은 "느린 네트워크 작업과 화면 그리기를 어떻게 안 부딪히게 분리했나"입니다. GUI에는 철칙이 하나 있습니다 — 화면을 그리는 코드는 절대 멈추면 안 된다(멈추면 창이 "응답 없음"이 됩니다). 그런데 SSH 접속·파일 전송은 몇 초씩 기다리는 일입니다. meatshell은 이 둘을 다른 일꾼에게 맡기고, 결과만 안전하게 화면으로 넘깁니다.
invoke_from_event_loop입니다. 다른 스레드의 결과를 안전하게 화면으로 넘기는 우체통 역할입니다.해피패스(정상 흐름) 한 줄기만 끝까지 따라가 봅니다. 곁가지(에러·재연결)는 생략합니다.
xterm-256color 타입 PTY를 요청해, 서버가 256색·전체화면 앱(vim·btop)을 정상적으로 그리게 합니다.SSH·시리얼·텔넷은 속이 완전히 다른데도, 모두 spawn_session처럼 똑같은 겉모습(SessionHandle + UnboundedReceiver<SessionEvent>)을 내놓도록 맞췄습니다. 그래서 터미널 화면 코드는 "이게 SSH인지 시리얼인지" 신경 쓸 필요 없이 그대로 돌아갑니다. 인터페이스를 통일해 재사용하는 전형적 패턴입니다.
큰 파일을 올리는 동안 터미널이 멈추면 곤란합니다. meatshell은 SFTP용으로 탭마다 SSH 연결을 하나 더 엽니다(셸 채널과 분리). 게다가 업로드는 응답을 기다리며 한 조각씩 보내는 대신, 32KiB 조각을 최대 32개까지 한꺼번에(파이프라인) 날려 왕복 지연을 숨깁니다 — 순진한 방식보다 훨씬 빠릅니다.
FinalShell처럼 왼쪽에 원격 서버의 CPU·메모리를 띄우려면 별도 에이전트가 필요할 것 같지만, meatshell은 더 영리합니다. 두 번째 채널에서 /proc/stat·/proc/meminfo·df를 2초마다 읽는 셸 루프를 돌려, 그 출력을 파싱합니다. 서버에 아무것도 설치하지 않고 모니터링을 해내는 실용적 트릭입니다.
원격에서 셸 명령을 돌려 그 출력을 파싱한다는 건, 오염된 환경(PATH 가로채기)·악의적 출력에 노출될 수 있다는 뜻입니다. meatshell은 모니터 루프 전에 PATH를 표준 경로로 리셋하고, 파싱은 전부 포화 연산(overflow 패닉 없음)으로, 항목 수는 64개·버퍼는 1MiB로 제한해 DoS를 막습니다. "외부 입력은 다 의심한다"는 보안 사고방식의 좋은 예입니다.
구조는 깔끔합니다 — 화면(ui/)과 로직(src/)이 완전히 분리돼 있습니다. README의 "프로젝트 배치"는 v0.1 시절이라 살짝 낡았고, 실제 코드는 시리얼·텔넷·프록시·SFTP·국제화·포트포워딩·ZMODEM 모듈까지 붙은 v0.4.6입니다(아래는 실제 트리).
| 파일 | 역할 |
|---|---|
| app.rs | 가장 큰 심장부. 탭별 터미널 버퍼 관리, vt100 파싱, 선택·검색·스크롤백, UI 콜백 연결 |
| ssh.rs | russh로 접속·인증·PTY, 셸 채널 + 원격 모니터 채널, OSC 7로 현재 경로 추적 |
| sftp.rs | 별도 SSH 연결로 파일 전송, 32개 동시 쓰기 파이프라인, 파일명 살균(경로 탈출 방지) |
| config.rs | 세션 영구 저장, ChaCha20-Poly1305 암호화, Secret 타입으로 평문 소거 |
| theme.slint | 단 하나의 Theme.dark 불리언으로 전체 색을 다크/라이트 전환 |
코드를 처음 본다면 main.rs(진입) → ui/theme.slint(가장 작고 직관적) → ssh.rs(접속의 전모) → app.rs의 spawn_session 주변(다리 역할) 순으로 보면 큰 그림이 잡힙니다. app.rs 4912줄을 처음부터 읽으려 하지 마세요 — 함수 이름으로 점프하는 게 정석입니다.
화면 구조를 .slint 파일에 선언적으로 적고, Rust 로직과 콜백으로 잇는 방식을 한 레포에서 통째로 볼 수 있습니다. 다크/라이트 테마를 토큰 하나로 전환하는 theme.slint가 특히 깔끔한 예제입니다.
.slint 파일에 버튼 하나와 텍스트를 두고, 버튼을 누르면 카운터가 오르는 미니 앱을 cargo run으로 띄워 본다."느린 일은 다른 스레드, 결과는 UI 스레드로"라는 패턴은 모든 데스크톱 앱의 기본기입니다. spawn으로 작업을 띄우고, 채널로 결과를 보내고, invoke_from_event_loop로 화면에 반영하는 삼박자를 익히세요.
tokio::time::sleep)을 버튼 클릭으로 띄우고, 끝나면 라벨이 바뀌게 만든다. 그동안 창이 멈추지 않는지 확인한다.russh로 접속→인증→채널→PTY 요청까지의 흐름은 SSH가 실제로 어떻게 동작하는지 배우는 살아있는 교본입니다. ssh.rs의 "셸 채널 + 모니터 채널" 2채널 구조를 따라가 보세요.
russh 예제로 로컬 서버에 접속해 ls 한 줄을 보내고 출력을 받아 찍는 30줄짜리 클라이언트를 짠다.터미널 화면은 단순한 글자 나열이 아니라, "커서를 (3,5)로 옮겨라", "이 줄을 지워라" 같은 ANSI 제어신호의 연속입니다. meatshell은 vt100 파서로 이를 격자로 해석하는데, btop·htop이 쓰는 일부 신호(HVP)가 라이브러리에 빠져 있어 직접 보정(HVP→CUP 재작성)하는 등 실전 디테일이 가득합니다.
echo -e "\e[31m빨강\e[0m 기본" 같은 ANSI 문자열을 vt100 파서에 넣어, 어느 칸이 무슨 색으로 해석되는지 출력해 본다.비밀번호를 ChaCha20-Poly1305로 암호화해 저장하고(config.rs), 메모리의 평문은 zeroize로 소거하며, 원격 파일명은 경로 탈출·셸 메타문자를 살균합니다. "기능"이 아니라 "안전"을 어디서 챙기는지 보는 좋은 사례입니다.
chacha20poly1305 크레이트로 짧은 문자열을 암호화·복호화하고, 같은 평문이라도 nonce가 다르면 암호문이 달라지는 걸 확인한다.| 항목 | 요구사항 |
|---|---|
| Rust 툴체인 | 1.75 이상 (rustup 권장). 빌드는 cargo run --release |
| Linux 추가 의존 | 시리얼(serialport)용 libudev, 창 표시용 그래픽 라이브러리(winit). Wayland 클립보드는 기능 플래그로 지원 |
| Windows | 추가 런타임 불필요 — 빌드 시 winresource가 .ico를 바이너리에 임베드 |
| macOS | Intel·Apple Silicon 둘 다 타깃. Info.plist 포함 |
| 설정 파일 위치 | Win: %APPDATA%/meatshell/ · Linux: ~/.config/meatshell/ · macOS: ~/Library/Application Support/meatshell/ |
| 하드웨어 | GPU 불필요. 메모리 목표가 수십 MB라 사양 부담이 매우 낮음 |
meatshell은 빠르게 성장 중인 프로젝트입니다(현재 v0.4.6 · ★692, 커밋·릴리스 활발히 진행 중). 로드맵상 known_hosts 검증·완전한 VT/ANSI 에뮬레이션·OS 키체인 비밀번호 저장 등은 아직 미완성이며, 서버 키 검증이 꺼져 있어 프로덕션·중요 서버에 그대로 쓰기엔 이릅니다. 학습·실험용으로는 훌륭하지만, 실사용은 최신 릴리스 노트를 확인하세요.
레포를 클론하고 cargo run --release로 띄운 뒤, "+ 새 세션"으로 로컬 또는 테스트 서버에 SSH로 붙어 본다. 왼쪽 사이드바에 원격 CPU·메모리가 뜨는지 확인하고, 설정 파일(~/.config/meatshell/sessions.json)을 열어 비밀번호가 enc:v1:로 암호화돼 있는지 본다.
ui/theme.slint의 색 토큰 한두 개(예: accent 색)를 바꿔 다시 빌드해, 화면 전체가 어떻게 따라 바뀌는지 관찰한다. "디자인 토큰 하나로 전체를 제어"하는 구조를 체감한다.
ssh.rs에서 원격 모니터 채널이 보내는 셸 루프(/proc/stat 등을 읽는 부분)를 찾아, 그 명령을 직접 서버 터미널에 붙여넣어 실제 출력이 어떻게 생겼는지 본다. 그다음 파싱 코드와 대조하며 "이 줄이 CPU%가 되는구나"를 매핑한다.
현재 무조건 통과하는 check_server_key를 고쳐, 서버 공개키 지문을 파일에 저장하고 다음 접속 때 바뀌면 경고하도록 만든다. SSH 보안의 핵심(MITM 방어)을 손으로 구현하며 익히는, 이 레포에서 가장 가치 있는 실습이다.
btop/htop 같은 전체화면 앱을 띄웠을 때 화면이 깨지는 ANSI 신호를 하나 찾아(또는 만들어), app.rs의 보정 로직(HVP→CUP 재작성 부근)을 본떠 새 케이스를 처리하게 한다. 터미널 에뮬레이션의 실전 난이도를 체감한다.
| 주차 | 주제 | 할 일 |
|---|---|---|
| 1주차 | Rust 기초 + 소유권 | 소유권·빌림·Arc/Mutex 이해, cargo로 작은 CLI 만들기 |
| 2주차 | tokio 비동기 | async/await·spawn·채널(mpsc) 익히기, 동시 작업 여러 개 굴려 보기 |
| 3주차 | Slint 데스크톱 GUI | .slint 문법·콜백·속성 바인딩, UI 스레드와 백그라운드 작업 연결 |
| 4주차 | SSH + 터미널 프로토콜 | SSH 채널·PTY 개념, ANSI 이스케이프·vt100 파싱, known_hosts 보안 |
meatshell은 "Rust로 진짜 데스크톱 앱을 끝까지 만들면 어떤 모듈들이 필요한가"의 지도와 같다. UI·비동기·네트워크·보안이 한 레포에 모여 있어, 위 4주를 끝내면 각 src/*.rs 파일이 "아, 이 주차에 배운 그거구나"로 읽히기 시작한다. 작은 레포라 통째로 정독하기에 부담도 적다.
ESC[31m(빨강)·ESC[2J(화면 지우기) 같은 제어신호로 색·커서·지우기를 표현한다. 이 규칙 모음이 ANSI 이스케이프이고, 그걸 해석해 화면 격자를 재구성하는 게 vt100 파서다.ESC]7;file://… 형식). meatshell은 셸에 PROMPT_COMMAND를 주입해 이 신호를 내보내게 하고, 그걸 파싱해 SFTP 패널이 같은 폴더를 따라가게 한다.known_hosts에 적어 두고, 다음에 지문이 바뀌면 "가짜 서버일 수 있다"며 경고해 이를 막는다. meatshell은 이 검증이 아직 꺼져 있어 직접 켜 보기 좋은 실습 대상이다.