본문으로 건너뛰기
5분 AI.
GITHUB TRENDING DART DAILY #1 딥다이브 · 2026-10-23 분석

ente 딥다이브
— 서버가 사진을 읽지 못하는 클라우드는 어떻게 만들까

앱·클라우드 서버

빠른 판단

무엇
기기에서 암호화한 사진·인증 코드·문서를 백업·공유·동기화하는 앱과 서버 모음
이럴 때
사진·2단계 인증·중요 문서 보관을 운영할 때
입력
사진·인증 정보·문서
결과
암호화된 클라우드 백업과 동기화·공유 결과
사진 백업 앱을 따라가며 암호화·비동기 동기화·데이터베이스·다중 플랫폼 설계를 배우는 공부자료입니다. Ente는 Photos, Auth, Locker 등을 같은 기반 위에 만드는 공개 소스 모노레포입니다. 모바일의 Dart에서 시작해 Go 서버와 Rust 공통 코드까지 연결합니다. 저장소 ente/ente · ⭐29,541 · 포크 1,874 · 주 언어 Dart · AGPL-3.0 · 2026-10-23 GitHub 기준. 코드와 의존성은 커밋 939ee85 기준입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

내 기기에서 잠근 데이터를 여러 기기에 안전하게 이어 주는 플랫폼

Ente는 사진·인증 코드·문서 등을 기기에서 암호화한 뒤 클라우드에 보관하고 동기화하는 서비스와 앱들의 모음입니다. 대표 제품 Photos는 사진 백업·앨범·공유를, Auth는 2단계 인증 코드를, Locker는 중요한 문서와 자격 증명 보관을 다룹니다. 루트 README.md가 소개하는 세 제품이며, 실제 디렉토리에는 Ensu, Cast, Space 등 더 넓은 개발 영역도 보입니다.

이 저장소의 핵심은 서버에 데이터를 맡기면서도, 내용의 복호화 권한은 사용자의 기기에 두는 설계입니다. 서버는 계정 확인·접근 권한·저장 용량·암호문 위치를 관리합니다. 사진 내용을 이해하지 않아도 이 업무는 할 수 있습니다. 반대로 사진을 화면에 표시하려면 클라이언트가 키를 풀고 데이터를 복호화해야 합니다.

용어
종단간 암호화 (E2EE)
보내는 기기에서 잠그고 허가받은 기기에서 여는 방식입니다. HTTPS가 통신 구간을 보호한다면, E2EE는 저장된 내용도 서비스 운영자가 키 없이 읽기 어렵게 만듭니다. 계정 식별자·파일 크기·접속 정보까지 전부 숨긴다는 뜻은 아닙니다.
한 문장 비유

창고에는 잠긴 상자를 맡기고, 열쇠 꾸러미는 내 손에 둡니다

창고 직원은 상자 번호와 무게를 알고, 누구에게 상자를 내줄지 확인합니다. 하지만 안에 든 사진을 볼 열쇠는 없습니다. 앱은 상자를 열어 사진을 보여 주고, 공유할 때는 상대방이 열 수 있도록 열쇠를 다시 감쌉니다.

이 비유에서 창고 관리자는 Go 서버 Museum, 상자 보관소는 S3 호환 저장소, 상자 장부는 PostgreSQL, 열쇠를 다루는 쪽은 모바일·웹·데스크톱·CLI 클라이언트입니다.

1-1. 사진 한 장을 올릴 때 벌어지는 일

  1. 클라이언트가 파일 키를 만들고 사진·썸네일·메타데이터를 암호화합니다.
  2. 파일 키를 앨범에 해당하는 컬렉션 키로 감싸서 함께 저장할 준비를 합니다.
  3. Museum에서 업로드 권한과 저장 위치를 받고, 암호문을 객체 저장소에 보냅니다.
  4. 파일 정보와 암호화된 키를 서버에 등록합니다. 다른 기기는 변경 내역을 받아 복호화합니다.

이 흐름은 web/packages/gallery/services/upload/upload-service.ts의 encryptFile()과 server/pkg/api/file.go의 CreateOrUpdate()로 연결해 읽을 수 있습니다. 화면을 그리는 코드보다 이 경로를 먼저 따라가면 제품의 중심을 빨리 잡을 수 있습니다.

학습 목표
사진 앱을 통해 분산 시스템의 기본기를 배웁니다

키의 계층, 네트워크 실패, 중복 실행, DB 트랜잭션, 언어 간 데이터 변환이 한 사용자 행동에 모입니다. 글을 마칠 때에는 “업로드 성공”이 단순히 HTTP 요청 하나의 성공과 왜 다른지 설명하는 것이 목표입니다.

2왜 주목받는가

트렌딩 순위와 관심을 끌 만한 기술적 특징을 구분해서 읽기

2-1. 일상적인 문제를 어려운 제약 아래 해결합니다

2026-10-23 GitHub Trending Dart Daily 1위라는 순위는 관심의 신호입니다. 다만 순위만으로 유입 경로나 인기의 원인을 알 수는 없습니다. 기능·공개된 구현·최근 변화는 관심을 끌 만한 요소지만, 특정 사건이 순위를 만들었다고 단정할 근거는 아닙니다.

사진 백업에는 자동 업로드, 여러 기기, 공유, 검색이 필요합니다. 여기에 서버가 사진을 복호화할 수 없다는 제약을 더하면, 썸네일 생성과 내용 기반 분석을 어디서 할지부터 다시 정해야 합니다. Ente에서는 클라이언트 암호화 코드와 로컬 ML 연결 코드를 함께 읽을 수 있습니다.

2-2. 어떤 대안과 비교하면 장점이 보일까

루트 README는 Photos를 Apple·Google Photos의 대안으로 소개합니다. 아래 표는 개별 경쟁 제품의 최신 요금이나 설정을 판정하는 비교가 아니라, 그런 대안을 고를 때 살펴볼 설계 방식의 비교입니다. Ente 열은 커밋 939ee85 기준입니다.

비교 축다른 접근에서 확인할 점Ente에서 보이는 장점과 비용
일반 사진 클라우드편리한 백업·공유와 함께, 복호화 키를 누가 통제하는지 확인업로드 전 암호화와 키 계층을 공개 코드로 추적 가능. 키 복구 설계가 더 복잡해짐
서버 분석형 사진 서비스서버가 원본을 분석하면 서버 연산 자원과 원본 접근 권한이 필요기기에서 분석하는 경로가 있음. 대신 기기 성능·배터리·모델 관리 부담이 생김
단순 암호화 파일 보관암호문 저장 외에 앨범·공유·썸네일·동기화를 직접 마련해야 하는지 확인사진 제품 기능과 암호화 계층을 함께 제공. 구현과 운영 부품 수가 많음
직접 만든 백업 서버클라이언트별 유지보수와 내보내기 경로까지 만들 수 있는지 확인모바일·웹·데스크톱·CLI 소스를 함께 학습 가능. 전체를 빌드하려면 여러 도구 체인이 필요
자체 호스팅API뿐 아니라 DB·객체 저장소·백업까지 운영 가능한지 확인Compose와 관리 문서가 있음. 서버 README는 자체 호스팅 지원의 제약도 명시

Ente의 학습 가치는 암호화 알고리즘 하나보다, 암호화를 유지한 채 제품의 편의 기능을 연결하는 과정에 있습니다. 예를 들어 앨범 공유는 단순히 DB의 공개 여부를 바꾸는 일이 아닙니다. 상대방에게 컬렉션 키를 안전하게 전달하는 문제까지 포함합니다.

2-3. 2026년 10월의 변화: Rust CLI

docs/docs/cli/migration.md는 새 CLI가 2026년 10월 0.4.0부터 도입됐고, 기존 Go CLI는 전환기에 계속 지원하되 새 개발은 Rust CLI에서 진행한다고 설명합니다. 2026-10-23 GitHub 기준 최신 릴리스는 ente-cli-v0.4.0입니다. 커밋 939ee85의 rust/apps/cli/Cargo.toml은 이미 0.4.1을 선언합니다. 발표 버전과 개발 버전이 다른 정상적인 사례입니다.

기존 cli/README.md의 ente account add와 새 문서의 ente account login --app photos는 서로 다른 세대의 명령입니다. 검색으로 찾은 명령이 어느 구현을 가리키는지 확인하는 습관도 배울 수 있습니다. 이 변화가 당일 트렌딩의 직접 원인인지는 확인되지 않았습니다.

비유

모노레포는 공장 단지와 비슷합니다. 간판은 Ente 하나지만 사진 앱 생산 라인과 인증 앱 생산 라인이 있고, 공용 부품 창고와 배송 관리실을 함께 씁니다. 특정 제품의 새 버전이 공장 전체의 모든 제품 버전을 뜻하지는 않습니다.

3기술 스택 전체 지도

이름을 외우기보다, 어느 책임 때문에 그 기술이 필요한지 연결하기

3-1. 프론트엔드와 클라이언트

영역실제 선언읽을 파일과 역할
모바일 언어Dart SDK >=3.10.0 <4.0.0mobile/pubspec.yaml: Photos·Auth·Locker와 공통 패키지를 workspace로 묶음
모바일 UICI Flutter 3.47.2.github/actions/setup-flutter/action.yml: SDK 버전과 OS별 다운로드 해시 고정
모바일 통신·저장dio 5.9.2, sqlite_async 0.11.7, flutter_secure_storage 9.0.0mobile/apps/photos/pubspec.yaml: HTTP·로컬 DB·비밀 저장소의 서로 다른 책임
모바일과 Rust 연결flutter_rust_bridge 2.13.0같은 pubspec 및 rust/Cargo.toml: Dart/Rust 양쪽 버전을 맞춤
웹 화면React 19.3.0, Next.js 16.4.2, MUI 9.4.0web/apps/photos/package.json, web/packages/base/package.json: 앱과 공용 UI 기반
웹 암호화·작업 분리libsodium-wrappers-sumo 0.7.15, comlink 4.4.2web/packages/base/package.json: 암호화 라이브러리와 Worker 호출 연결
데스크톱Electron 42.10.0, electron-builder 26.15.3desktop/package.json: 웹 Photos 화면을 렌더러로 재사용하고 네이티브 기능을 연결

모바일 Photos 앱 선언 버전은 1.3.67+2158, 데스크톱 패키지는 1.7.31-beta입니다. 앱 버전·라이브러리 버전·SDK 제약은 서로 다른 숫자입니다. Flutter 버전은 앱 pubspec의 sdk: flutter만으로 알 수 없으므로 CI 설정까지 읽어야 합니다.

용어
의존성 선언과 잠금 파일
manifest는 필요한 패키지와 허용 버전을, lock 파일은 해석된 의존성 조합을 기록합니다. mobile/pubspec.lock, web/package-lock.json, rust/Cargo.lock을 함께 보세요. 이 글의 버전 표는 명시한 manifest·CI 선언 기준이며, 설치 결과나 최신 공개 버전을 뜻하지 않습니다.

3-2. 백엔드와 공통 코어

기술버전·설정사용 이유와 근거
Gogo 1.26.0, toolchain go1.26.10server/go.mod: Museum 서버. 기존 cli/go.mod도 같은 Go 선언
Gin·PostgreSQL 연결gin v1.9.1, lib/pq v1.8.0server/go.mod: HTTP 처리와 SQL 드라이버. 저장 계층은 직접 SQL을 작성
설정·마이그레이션viper v1.8.1, migrate/v4 v4.12.2server/go.mod: 설정 병합과 DB 스키마 변경 이력
객체 저장소 SDKaws-sdk-go v1.34.13server/go.mod: S3 호환 API와 서명된 URL 생성
Rustedition = "2024", workspace resolver = "2"rust/Cargo.toml: 여러 앱·공통 crate·바인딩을 한 workspace로 관리
Rust 실행·통신·DBtokio 1.53.1, reqwest 0.12.28, rusqlite 0.40.2rust/Cargo.toml: 비동기 작업, HTTP, 로컬 SQLite 기반
Rust 암호화argon2 0.6.0, crypto_secretbox 0.1.0, crypto_secretstream =0.2.0rust/crates/core/Cargo.toml: 비밀번호 기반 키 유도와 libsodium 호환 암호문 처리
Rust MLort =2.0.0-rc.13rust/Cargo.toml: ONNX Runtime 연결. 릴리스 후보 버전까지 정확히 고정
ML 연구·검증Python >=3.12, onnxruntime==1.28.0, numpy>=2.1.2infra/ml/pyproject.toml: 모델 실험·검증 영역. Museum의 Python 백엔드라는 뜻은 아님

같은 저장소에 있다고 해서 모든 앱이 모든 기술을 실행하는 것은 아닙니다. 예를 들어 Rust CLI는 SQLCipher를 활성화한 로컬 저장소와 OS 키링을 사용하도록 의존성을 선언하지만, 서버 PostgreSQL과 역할이 다릅니다. rust/apps/cli/Cargo.toml의 feature 목록이 이 차이를 드러냅니다.

3-3. 인프라·빌드·CI

버전을 읽는 연습
직접 고친 포크도 제품의 중요한 의존성입니다

모바일 Photos의 computer, flutter_sodium은 Git 커밋으로 고정된 의존성입니다. mobile/pubspec.yaml에는 캐시 삭제·비밀 저장소·동영상 문제에 대한 override와 이유도 적혀 있습니다. 패키지 이름뿐 아니라 “왜 이 버전에 머무르는가”까지 읽는 것이 유지보수 공부입니다.

4아키텍처 심화 분석

사진 한 장을 기준으로 네 가지 설계 패턴을 연결합니다

4-1. 전체 시스템: 제어 정보와 파일 본문은 다른 길을 갑니다

[사용자의 기기: 복호화 가능한 영역] Flutter 앱 / 웹 앱 / Electron 앱 / Rust CLI | 로컬 DB, 키, 암호화, 일부 분석 | +--- 계정·앨범·업로드 권한·변경 내역 ---> [Museum / Go + Gin] | | | +---> [PostgreSQL] | | 계정, 접근 권한, | | 암호화된 키·메타데이터, | | 객체 위치·변경 시각 | | | 서명된 업로드 URL <------------+ | +--- 암호화된 파일·썸네일 ----------------> [S3 호환 객체 저장소] ^ | [Museum의 복제·정리 작업] 다운로드: 객체 받기 -> 기기에서 키 풀기 -> 복호화 -> 화면 표시

일반적인 서명 URL 업로드 경로를 단순화한 도식입니다. 모든 요청이 같은 경로를 쓰는 것은 아닙니다. 서버는 파일 내용을 읽지 않아도 소유권·용량·업로드 완료 여부를 검사할 수 있습니다. server/ente/file.go의 File에는 OwnerID, CollectionID, EncryptedKey, KeyDecryptionNonce가 함께 들어갑니다.

4-2. 패턴 ① 키를 다른 키로 감싸는 계층

비밀번호 + salt -- Argon2id --> keyEncryptionKey | | masterKey를 풀기 v masterKey | | 내 컬렉션 키를 풀기 v collectionKey | | 파일 키를 풀기 v fileKey | +--- 사진·썸네일·메타데이터를 풀기 공유받은 컬렉션: 내 비밀키로 전달받은 collectionKey를 풀어 같은 하위 흐름 사용

architecture/README.md가 설명하는 키 계층은 업로드 구현에서도 확인됩니다. web/packages/gallery/services/upload/upload-service.ts의 encryptFile()은 generateBlobOrStreamKey()로 파일 키를 만들고, 마지막에 encryptBox(fileKey, collectionKey)를 호출합니다.

// encryptFile()의 실제 호출을 뽑아 흐름만 줄인 예시
const fileKey = await worker.generateBlobOrStreamKey();
const encryptedMetadata = await worker.encryptMetadataJSON(
    metadata,
    fileKey,
);
const encryptedFileKey = await worker.encryptBox(fileKey, collectionKey);
// 원본 함수는 사진 본문·썸네일·추가 메타데이터도 처리합니다.

모바일에서도 mobile/apps/photos/lib/module/upload/service/file_uploader.dart의 _tryToUpload() 안에 파일 키와 컬렉션 키를 CryptoUtil.encryptSync()로 연결하는 코드가 있습니다. 새 파일 등록 전에 decryptVerify(..., chunkLimit: 1)로 첫 청크를 대상으로 검증하는 경로도 보입니다. 이것을 파일 전체를 매번 검증한다는 의미로 확대하면 안 됩니다.

복호화 쪽은 rust/crates/collections/src/lib.rs의 open_collection_key()가 좋은 입구입니다. 내가 소유한 컬렉션이면 secretbox::decrypt()와 session.master_key를, 공유받은 것이면 sealed::open()과 내 비밀키를 사용합니다. 같은 반환 타입 Key가 하위 계층을 단순하게 만듭니다.

용어
봉투 암호화와 nonce
큰 데이터는 임의의 파일 키로 잠그고, 작은 파일 키를 상위 키로 다시 잠그는 방식입니다. nonce는 암호화할 때 쓰는 별도의 값으로, 비밀번호나 비밀키가 아닙니다. secretbox.rs의 encrypt()는 Nonce::generate()를 호출해 암호문과 nonce를 함께 반환합니다.

큰 파일에서는 “끝까지 받았는가”도 검사해야 합니다

rust/crates/core/src/crypto/stream.rs는 ENCRYPTION_CHUNK_SIZE = 4 * 1024 * 1024, 즉 4 MiB 단위 처리를 정의합니다. Encryptor는 마지막 조각에 Tag::Final을 붙이고, Decryptor::finish()는 그 표식을 못 봤으면 Error::StreamTruncated를 반환합니다.

// rust/crates/core/src/crypto/stream.rs: 실제 메서드
pub fn finish(self) -> Result<()> {
    if self.seen_final {
        Ok(())
    } else {
        Err(Error::StreamTruncated)
    }
}

조각마다 인증에 성공해도 마지막 몇 조각이 사라질 수 있습니다. 그래서 “읽을 데이터가 더 없다”와 “정상적인 마지막 표식을 확인했다”를 구분합니다. 이 파일의 test_decrypt_file_truncation_at_chunk_boundary() 같은 테스트 이름만 읽어도 중요한 실패 조건을 배울 수 있습니다.

4-3. 패턴 ② HTTP 처리·업무 규칙·저장소를 나눕니다

server/pkg/api/file.go FileHandler.CreateOrUpdate() | JSON 해석, 사용자 ID, 변경 시각, 응답 v server/pkg/controller/file.go FileController.Create() / GetUploadURLWithMetadata() | 크기·용량·권한·업로드 객체 관련 규칙 v server/pkg/repo/file.go FileRepository.Create() | SQL과 트랜잭션 v files + collection_files + object_keys + 사용량·복제 관련 기록

CreateOrUpdate()는 file.ID == 0이면 생성, 아니면 갱신으로 보냅니다. GetUploadURLWithMetadata()는 양수인 ContentLength, 허용 크기, MD5 값, 사용량을 확인합니다. 기존 묶음 API의 GetUploadURLs()에는 MaxUploadURLsLimit = 50 제한도 있습니다. 이 50은 사진 총개수 제한이 아니라 해당 API가 한 번에 발급할 URL 수의 제한입니다.

FileRepository.Create()는 BeginTx()로 시작하고 defer tx.Rollback()을 둡니다. 파일 행, 앨범과 파일의 연결, 객체 위치, 사용량, 복제 필요 기록을 처리한 뒤 Commit()합니다. main()은 이 repository와 controller를 구조체 필드로 조립합니다. 이름만으로 완벽한 계층 분리를 가정하기보다 실제 의존 방향을 보는 것이 좋습니다.

객체 업로드와 DB 등록은 서로 다른 작업이므로, 실패한 중간 상태를 정리하는 설계까지 있어야 업로드가 완성됩니다. server/pkg/controller/file.go의 getObjectURL()은 임시 객체 정보를 다루고, repository의 생성 경로는 ObjectCleanupRepo.RemoveTempObjectKey()와 markAsNeedingReplication()을 같은 DB 트랜잭션 안에서 호출합니다. DB 트랜잭션이 외부 객체 저장소까지 함께 되돌린다고 이해하면 안 됩니다.

4-4. 패턴 ③ 중복 실행을 합치고 변경분만 받습니다

mobile/apps/photos/lib/services/sync/sync_service.dart의 SyncService.sync()는 이미 실행 중이면 _existingSync!.future를 반환합니다. 새 동기화를 여러 개 만들지 않고 한 작업의 결과를 공유하는 방식입니다. 성공 여부와 관계없이 finally에서 completer를 완료하고 참조를 비웁니다.

// sync()의 중복 실행 처리 부분: 실제 소스 발췌
if (_existingSync != null) {
  _logger.warning("Sync already in progress, skipping.");
  return _existingSync!.future;
}
_syncStopRequested = false;
_existingSync = Completer<bool>();

연결 상태 변화, 계정 설정, 권한 허용 등 여러 사건이 동기화를 요청할 수 있습니다. 이 보호는 같은 SyncService 인스턴스의 실행 겹침을 다루며, 모든 기기의 분산 잠금을 뜻하지 않습니다. 이벤트 버스는 화면에 진행 상황을 알리는 통로이고 서버가 보내는 메시지 큐와도 구분해야 합니다.

remote_sync_service.dart의 _syncCollectionDiff(collectionID, sinceTime)는 getEncryptedFilesDiff()를 호출하고 삭제·갱신 내역을 반영합니다. 이어 latestUpdatedAtTime을 저장하고 hasMore이면 다음 페이지를 가져옵니다. 체크포인트를 언제 갱신하는지, 삭제가 어떤 형태로 전달되는지를 함께 읽어 보세요.

비유

매번 도서관의 모든 책을 새로 복사하는 대신 “마지막 장부 확인 이후 바뀐 책” 목록을 받습니다. 체크포인트는 마지막으로 읽은 장부 위치입니다. 장부 위치만 먼저 옮기고 변경을 저장하지 못하면 다음 조회에서 빠뜨릴 수 있습니다.

4-5. 패턴 ④ 공통 기능과 플랫폼 어댑터의 경계를 둡니다

rust/bindings/frb/photos/src/api/ml_indexing_api.rs의 analyze_image_rust()는 요청을 공통 형태로 바꾸고 spawn_blocking_with()에서 indexing::analyze_image()를 실행합니다. rust/bindings/napi/photos/src/lib.rs의 AnalyzeImageTask::compute()도 같은 공통 분석 함수를 부릅니다. Flutter와 Electron의 호출 방식은 달라도 분석 엔진을 함께 쓸 수 있는 구조입니다.

웹은 또 다른 경계가 있습니다. web/packages/base/crypto/index.ts의 createComlinkCryptoWorker()가 Worker를 만들고, web/packages/base/crypto/worker.ts의 CryptoWorker가 libsodium 함수들을 노출합니다. 암호화 작업이 화면의 주 실행 흐름을 오래 붙잡지 않도록 하는 구성입니다.

Rust의 WASM 바인딩도 있지만 웹 암호화가 전부 Rust로 바뀐 것은 아닙니다. rust/bindings/wasm/photos/Cargo.toml은 공통 WASM 모듈의 기능을 선택하고, 앞서 본 웹 업로드 경로는 CryptoWorker를 사용합니다. 공통화된 부분과 아직 별도로 유지되는 부분을 함께 읽는 것이 실제 모노레포 분석입니다.

5디렉토리 구조 해부

폴더 이름을 “어느 제품인가”와 “어느 계층인가”로 나눠 읽기
ente/ ├── README.md / LICENSE / SECURITY.md ├── architecture/ 암호화 개념 문서와 도식 ├── mobile/ │ ├── pubspec.yaml Dart workspace │ ├── apps/ │ │ ├── photos/ 사진 앱, 동기화, 업로드, 플랫폼 플러그인 │ │ ├── auth/ 인증 코드 앱 │ │ └── locker/ 문서·자격 증명 앱 │ └── packages/ 계정, 통신, UI, 번역 등 공통 패키지 ├── web/ │ ├── apps/ photos, auth, locker, accounts, albums 등 │ └── packages/ base, gallery, media, WASM 래퍼 등 ├── desktop/ Electron 메인 프로세스와 웹 화면 연결 ├── server/ │ ├── cmd/museum/main.go 서버 구성·라우팅·실행 진입점 │ ├── ente/ 요청·응답과 도메인 데이터 타입 │ ├── pkg/api/ HTTP 해석과 응답 │ ├── pkg/controller/ 업무 규칙·서비스 조정 │ ├── pkg/repo/ SQL 접근과 저장 처리 │ ├── pkg/middleware/ 인증 등 공통 요청 처리 │ ├── migrations/ PostgreSQL 스키마 변경 SQL │ └── compose.yaml / Dockerfile 서버 구성과 이미지 빌드 ├── rust/ │ ├── apps/cli/ 새 Rust CLI │ ├── apps/ensu/ Ensu 앱과 Tauri 관련 코드 │ ├── crates/ core, accounts, photos, ml, collections 등 │ ├── bindings/ frb, napi, wasm, uniffi 연결 계층 │ └── tools/ 코드 생성 등 개발 도구 ├── cli/ 기존 Go CLI ├── apple/ / android/ 별도 네이티브 앱·공통 플랫폼 코드 ├── infra/ 서비스 운영, Workers, ML 실험 등 ├── docs/ 공개 도움말과 개발 관련 문서 └── .github/ CI, 공통 Actions, 릴리스 도구

mobile/apps/photos와 web/apps/photos는 같은 제품의 다른 클라이언트입니다. 반면 server/pkg/api와 server/pkg/repo는 같은 서버의 다른 계층입니다. 이 두 분류를 혼동하면 “사진 코드가 왜 여러 군데 있지?”라는 의문이 생깁니다.

5-1. 처음에는 이 여섯 지점만 읽어도 됩니다

순서파일답해 볼 질문
1architecture/README.md각 키는 무엇을 잠그고, 어디에 암호화된 형태로 저장되는가?
2web/packages/gallery/services/upload/upload-service.tsencryptFile()의 입력과 반환값에 평문 키가 어떻게 구분되는가?
3server/ente/file.go서버가 받는 파일 레코드에는 어떤 필드가 보이는가?
4server/pkg/api/file.go → server/pkg/controller/file.go → server/pkg/repo/file.go입력 해석·규칙 검사·SQL 실행은 어디서 나뉘는가?
5mobile/apps/photos/lib/services/sync/sync_service.dart같은 작업이 동시에 요청되면 어떤 Future를 돌려주는가?
6rust/crates/core/src/crypto/stream.rs정상 종료, 손상, 잘림을 어떻게 구분하는가?

그다음 mobile/apps/photos/lib/db에서 기기의 장부를, server/migrations에서 서버 장부의 변경 이력을 보세요. 양쪽 모두 파일을 다루지만 로컬 파일과 서버 파일을 연결해야 하는 문제와 계정 전체의 권한을 관리하는 문제는 다릅니다.

읽는 요령
생성된 연결 코드보다 손으로 쓴 어댑터를 먼저 보세요

rust/bindings/frb/photos/src/lib.rs에는 frb_generated 모듈이 연결됩니다. 처음부터 생성 코드 전체를 해독할 필요는 없습니다. rust/bindings/frb/photos/src/api/ml_indexing_api.rs에서 요청 타입, 공통 함수 호출, 오류 변환부터 읽으면 경계를 이해하기 쉽습니다.

6학습 포인트 (기술별)

문법을 제품의 실패 조건과 연결해서 공부하기

6-1. Dart·Flutter: 비동기는 화면 밖에서도 중요합니다

Future는 나중에 끝나는 결과이고 Completer는 그 결과를 직접 완료시키는 도구입니다. SyncService.sync()에서 completer를 저장해 두는 이유는 여러 요청이 같은 진행 중 작업을 가리키게 하기 위해서입니다. finally가 빠지면 예외 이후에도 “실행 중” 상태가 남을 수 있다는 점을 생각해 보세요.

_registerListeners()는 연결 상태·계정 설정·구독 변경·동기화 상태를 듣습니다. 실습으로 각 이벤트의 발생 시점과 sync() 진입 조건을 표로 그려 보세요. UI 버튼 하나만 생각하는 예제보다 실제 앱에서 왜 중복 방지가 필요한지 잘 드러납니다.

6-2. Go·HTTP: 입력을 받는 함수와 결정을 내리는 함수를 나눕니다

FileHandler.CreateOrUpdate()에서 JSON 바인딩과 서버 시각 부여를 찾고, FileController.GetUploadURLWithMetadata()에서 용량 제한을 찾으세요. 실습은 “크기 0, 허용 크기 초과, 잘못된 MD5, 잔여 용량 부족”을 각각 어느 조건이 거절하는지 연결하는 것입니다.

server/pkg/middleware/auth.go의 TokenAuthMiddleware()도 같이 읽으세요. 토큰 검사와 앱 구분 뒤 사용자 ID를 요청 헤더에 설정합니다. 로그인한 사용자라는 사실과 특정 앨범을 수정할 수 있다는 권한을 별도로 확인해야 하는 이유를 설명해 보세요.

6-3. PostgreSQL: 테이블보다 먼저 불변 조건을 찾습니다

FileRepository.Create()는 파일 행 하나만 삽입하지 않습니다. 앨범 연결, 객체 위치, 사용량도 일치해야 합니다. 실습으로 SQL 단계마다 실패 표시를 놓고, Rollback()이 어떤 DB 변경을 되돌리는지 적어 보세요. 이미 S3에 올라간 암호문은 이 목록에 들어가지 않습니다.

용어
트랜잭션과 불변 조건
트랜잭션은 묶인 DB 변경을 함께 확정하거나 취소하는 단위입니다. 불변 조건은 “파일 등록이 확정됐다면 앨범 연결과 사용량도 맞아야 한다”처럼 항상 유지해야 할 규칙입니다. 먼저 규칙을 말로 적으면 무엇을 한 트랜잭션으로 묶을지 판단하기 쉬워집니다.

6-4. Rust·암호화: 안전한 타입과 실패 경로를 읽습니다

rust/crates/core/src/crypto/secret.rs의 SecretVec·SecretString은 Zeroize와 ZeroizeOnDrop을 사용하고, 디버그 출력은 비밀 대신 [REDACTED]를 반환합니다. into_vec()처럼 일반 값으로 꺼내는 경로도 있으므로 모든 복사본이 자동으로 지워진다고 확대해서는 안 됩니다.

실습으로 secretbox.rs의 짧은 암호문 거절, 잘못된 키 테스트, stream.rs의 잘림 테스트를 읽으세요. 같은 결과를 다시 복호화하는 정상 사례뿐 아니라, 어떤 입력을 거절해야 하는지가 라이브러리 계약의 절반입니다.

6-5. 웹·Worker·FFI: 경계에서 생기는 비용을 봅니다

web/packages/base/crypto/index.ts는 Worker 안인지 확인해 직접 호출과 원격 호출을 나눕니다. Comlink는 다른 실행 흐름의 함수를 비동기 함수처럼 부르게 돕습니다. 화면이 멈추지 않는다는 이점과 별개로 데이터 전달·복사·오류 전달 비용은 남습니다.

실습으로 Flutter의 AnalyzeImageRequest와 N-API의 같은 목적 요청을 비교하세요. 전자는 image_path로 파일 위치를, 후자는 image_bytes로 바이트를 받는 경로가 있습니다. 공통 분석 엔진을 재사용하더라도 플랫폼에 맞춘 입력 변환이 필요한 이유를 설명할 수 있습니다.

6-6. ML·검색: 모델보다 데이터 이동을 먼저 봅니다

ml_indexing_api.rs의 init_ml_runtime()과 analyze_image_rust()는 ensure_indexing_models()를 거쳐 모델 경로를 확보합니다. 요청의 run_faces, run_clip, run_pets로 분석 종류를 선택합니다. 모델 다운로드와 분석 실행은 서로 다른 실패를 가진 작업입니다.

실습은 결과의 얼굴 목록과 CLIP 임베딩을 구분해 표로 정리하는 것입니다. 사진을 숫자 벡터로 바꾸는 단계와, 벡터를 저장·검색하는 단계도 다릅니다. rust/crates/photos/src/ml_db와 rust/crates/vecdb가 다음으로 읽을 곳입니다.

6-7. CI·릴리스: 테스트 실행 단위도 아키텍처입니다

.github/workflows/rust-test.yml은 workspace와 CLI를 별도 suite로 둡니다. 주석은 SQLCipher feature가 다른 SQLite 테스트에 합쳐지지 않도록 분리한다고 설명합니다. cargo test --locked와 실행 대상 분리는 단순 속도 조절을 넘어 의존성 조합을 통제하는 역할을 합니다.

한 기능을 배울 때는 정상 동작 하나와 실패 조건 하나를 반드시 짝지어 읽으세요. 동기화에는 중복 요청, 암호화에는 잘림, DB에는 부분 실패, 모델에는 다운로드 실패를 붙이면 기술 이름을 외우는 공부에서 시스템을 이해하는 공부로 넘어갈 수 있습니다.

7하드웨어 / 시스템 요구사항

서비스 실행 최소치와 전체 제품 개발에 필요한 자원은 다릅니다

7-1. 서버 문서가 명시하는 최소 조건

docs/docs/self-hosting/installation/requirements.md는 quickstart 클러스터에 CPU 1코어, RAM 1 GB를 최소 조건으로 적습니다. 이 수치는 공식 문서 기준이며, 대량 라이브러리의 처리량을 측정한 벤치마크나 소스 빌드 권장 사양은 아닙니다.

항목확인된 조건초보자가 구분할 점
서버 OSLinux 또는 Unix 계열. 문서는 Ubuntu·Debian 권장Docker의 파일 권한·네트워크 동작도 운영 조건에 포함
컨테이너문서의 Compose 설치 경로는 Docker와 Compose plugin 필요Compose 2.30 이상. docker compose 명령 기준
파일 시스템PostgreSQL 컨테이너는 사용자·그룹 권한을 지원하는 저장소 필요문서는 ZFS·EXT4·BTRFS 등을 예시로 제시
스토리지PostgreSQL 데이터와 S3 호환 객체 저장소 모두 필요사진 원본뿐 아니라 DB·썸네일·복제·백업 용량도 계산
로컬 포트server/compose.yaml: API 8080, 객체 API 3200컨테이너 안의 localhost와 사용자 기기의 localhost는 다른 주소 공간
모바일 PhotosAndroid minSdk = 26, iOS 15.1mobile/apps/photos/android/app/build.gradle과 mobile/apps/photos/ios/Podfile 기준. 다른 앱 전체에 같은 값을 적용할 수 없음

모바일 최소 버전은 Photos 앱의 설정 기준입니다. 서버 README에는 Docker 없이 단일 바이너리로 실행하는 선택지도 있으므로, Docker가 Go 서버 자체의 필수 런타임이라는 뜻은 아닙니다. 다만 DB와 객체 저장소는 별도로 준비해야 합니다.

7-2. 메모리 사용은 클라이언트에서도 생깁니다

rust/crates/core/src/crypto/argon.rs의 기본 상수는 INTERACTIVE 64 MiB·2회, MODERATE 256 MiB·3회, SENSITIVE 1 GiB·4회입니다. 그런데 derive_sensitive_adaptive()는 실제로 256 MiB·16회에서 시작하고, 실패하면 메모리를 절반으로 줄이고 반복 횟수를 늘려 128 MiB까지 시도합니다.

이는 architecture/README.md의 “SENSITIVE 한계에서 시작해 조절”이라는 개념 설명보다 구체적인 구현입니다. 상수 SENSITIVE가 존재한다고 실제 시작 메모리가 언제나 1 GiB인 것은 아닙니다. 또한 이 비용은 키 유도 작업의 값이지 로그인 전체나 앱 전체의 메모리 상한이 아닙니다.

비유

자물쇠 열쇠를 만들 때 넓은 작업대를 쓰면 반복 작업을 적게 하고, 작업대가 작으면 더 여러 번 작업하는 방식입니다. 구현은 메모리와 반복 횟수의 곱을 유지하도록 조절하지만, 그 곱 하나로 모든 환경의 공격 비용이 같다고 증명되는 것은 아닙니다.

7-3. 개발 PC 자원은 학습 범위에 맞춰 잡습니다

저장소에는 모든 제품을 한꺼번에 개발하는 PC의 공식 최소 RAM이 명시돼 있지 않습니다. 다음 수치는 학습을 위한 여유 자원 제안이며 측정된 공식 요구사항이 아닙니다. 소스 읽기만 할 때에는 전체 SDK를 설치할 필요가 없습니다.

학습 범위준비할 도구자원 계획
코드 추적편집기, Git, 검색 도구일반 개발 PC로 시작. 대형 생성 파일을 한 번에 모두 열지 않기
서버 개발Go 1.26 계열, DB·객체 저장소 또는 DockerRAM 4 GB 이상 여유를 잡는 학습 계획. 최소 운영치와 구별
웹·Rust 개발Node 24 계열, npm, Rust와 WASM 대상RAM 8~16 GB, 수십 GB 디스크 여유를 고려. 캐시·도구 체인 크기에 따라 변동
모바일·ML 개발Flutter, 대상 플랫폼 SDK, Rust, 모델 자산RAM 16 GB 이상을 계획하면 에뮬레이터 병행에 유리. iOS 빌드는 macOS·Xcode 필요

rust/crates/ml/Cargo.toml에는 OS별 Core ML·WebGPU·XNNPACK 관련 feature가 나뉩니다. 이것이 서버에 GPU가 필수라는 뜻은 아닙니다. 분석 장치와 모델, 활성화된 실행 경로에 따라 실제 성능이 달라지므로 특정 초당 처리량은 제시하지 않습니다.

백업의 조건
암호화된 사진 파일만 복사해서는 복구가 완성되지 않습니다

docs/docs/self-hosting/administration/backup.md는 암호문, DB의 암호화된 키·연결 정보, 사용자 측 키 복구 수단을 함께 고려하라고 설명합니다. 객체 저장소의 3개 버킷이 같은 로컬 디스크에 있다면 독립된 3개 클라우드 백업이 아닙니다. server/compose.yaml도 운영용이 아닌 예제임을 명시합니다.

8직접 해볼 수 있는 실습 과제

코드 읽기와 작은 독립 예제로, 제품의 핵심 조건을 재구성합니다

첫 실습부터 계정이나 실제 사진을 준비할 필요는 없습니다. 읽은 코드의 입력·출력·실패 조건을 작은 결과물로 남기면 됩니다. 실습의 완료 기준은 코드를 많이 읽는 것이 아니라, 특정 실패 상황에서 데이터가 어떻게 남는지 설명하는 것입니다.

과제 1 · 예상 30~45분

사진 한 장의 경로에 이름 붙이기 입문

읽을 곳: architecture/README.md, server/ente/file.go, web/packages/gallery/services/upload/upload-service.ts의 encryptFile().

  1. 사진 본문, 썸네일, 메타데이터, 파일 키, 컬렉션 키를 각각 카드 하나에 적습니다.
  2. 누가 무엇을 암호화하는지 화살표를 그립니다. 전송되는 것은 평문인지 암호문인지도 적습니다.
  3. 서버가 보는 필드와 기기에서만 사용하는 키를 두 칸 표로 정리합니다.

완료 기준: EncryptedKey와 KeyDecryptionNonce가 왜 별도 필드인지, OwnerID가 보이는 것이 사진 내용의 평문 저장과 왜 다른지 설명할 수 있습니다.

과제 2 · 예상 45~60분

동시에 두 번 눌린 동기화 추적하기 입문

읽을 곳: mobile/apps/photos/lib/services/sync/sync_service.dart의 sync(), existingSync(), finally 블록.

  1. 시간축에 A 호출, B 호출, 작업 성공, C 호출을 차례로 놓습니다.
  2. 각 시점의 _existingSync 값과 공유되는 Future를 적습니다.
  3. 성공 자리를 예외로 바꾸고 정리 코드가 실행되는지 다시 추적합니다.

완료 기준: A와 B는 진행 중 작업을 공유하고 C는 새 작업을 시작한다는 표를 만듭니다. 일반 catch의 예외 재전파와 complete(successful)의 불리언 전달이 어떻게 다른지도 확인하세요. 덧붙여 existingSync()의 주석이 왜 일반적인 대기 용도를 경계하는지 적어 보세요.

과제 3 · 예상 45~90분

4 MiB 경계에서 마지막 청크 찾기 중급

읽을 곳: rust/crates/core/src/crypto/stream.rs의 StreamingEncryptor::finish(), estimate_encrypted_size(), 잘림 테스트.

아래는 암호화를 실행하지 않고 청크 수만 계산하는 독립 예제입니다. 0바이트에도 인증을 위한 마지막 청크가 필요하다는 점을 반영합니다.

# 저장소 실행과 무관한 경계값 계산 예제
CHUNK = 4 * 1024 * 1024
for size in (0, CHUNK - 1, CHUNK, CHUNK + 1):
    chunks = max(1, (size + CHUNK - 1) // CHUNK)
    print(size, chunks)
# 청크 수: 차례대로 1, 1, 1, 2

완료 기준: 각 경우 마지막 표식이 붙을 청크를 표시하고, 마지막 청크가 사라졌을 때 StreamTruncated가 필요한 이유를 설명합니다. 파일 크기 추정에는 청크별 ABYTES와 별도 24바이트 헤더를 구분하세요. 해당 추정 함수는 별도 헤더를 포함하지 않습니다.

과제 4 · 예상 60~90분

업로드의 부분 실패 지도 만들기 중급

읽을 곳: server/pkg/controller/file.go의 getObjectURL(), server/pkg/repo/file.go의 Create().

  1. URL 발급 뒤 업로드 안 함, 객체 업로드 뒤 DB 실패, DB 확정 뒤 응답 유실을 행으로 만듭니다.
  2. 각 행에 임시 객체 기록·실제 객체·파일 행·사용량 기록이 남을 가능성을 적습니다.
  3. 직접 확인된 처리와 추가로 조사할 재시도·중복 처리 조건을 구분합니다.

완료 기준: DB 롤백이 외부 객체 삭제까지 보장하지 않는다는 설명과, 임시 객체 추적의 필요성을 담은 표를 작성합니다. 응답이 유실된 경우의 정확한 재시도 동작은 추가 코드를 보기 전 단정하지 마세요.

과제 5 · 예상 90~150분

두 플랫폼의 ML 어댑터 비교하기 고급

읽을 곳: rust/bindings/frb/photos/src/api/ml_indexing_api.rs와 rust/bindings/napi/photos/src/lib.rs.

  1. 입력에서 파일 ID, 이미지 위치 또는 바이트, 분석 선택값을 찾습니다.
  2. 공통 indexing::AnalyzeImageRequest로 바뀌는 지점을 표시합니다.
  3. 실행 흐름 분리, 결과 변환, 오류 변환을 각 플랫폼에서 짝지어 정리합니다.

완료 기준: 공통 분석 엔진을 수정하는 경우와 바인딩만 수정하는 경우를 각각 한 예씩 제시합니다. 이미지 바이트 복사가 발생하는 지점도 표시하면 데이터 전달 비용까지 공부할 수 있습니다.

9관련 기술 심화 학습 로드맵

6주 동안 한 사용자 흐름을 점차 깊게 이해하는 순서

하루에 폴더 하나씩 끝내려고 하면 규모에 압도되기 쉽습니다. 주당 4~6시간 정도를 가정한 학습 계획이며, 각 주의 마지막에 설명 그림이나 표 하나를 남깁니다. 처음 접하는 언어는 모든 문법을 끝내기보다 함수 입력·출력·조건문부터 읽어도 됩니다.

주차먼저 익힐 개념Ente에서 읽을 곳남길 결과물
1주HTTP 요청·JSON·계정과 권한·암호문server/ente/file.go, architecture/README.md파일 한 장과 키의 관계도. 서버가 알아야 하는 정보 5개
2주Dart의 Future·Completer·이벤트·예외 정리mobile/apps/photos/lib/services/sync/sync_service.dart, mobile/apps/photos/lib/services/sync/remote_sync_service.dart중복 요청 시간축과 증분 동기화의 체크포인트 표
3주Go 구조체·메서드·SQL·트랜잭션server/pkg/api/file.go → server/pkg/controller/file.go → server/pkg/repo/file.go입력 검사표와 부분 실패 지도
4주키 유도·인증 암호화·스트림 종료·Rust의 Resultrust/crates/core/src/crypto/argon.rs, rust/crates/core/src/crypto/secretbox.rs, rust/crates/core/src/crypto/stream.rs암호화 방식 비교와 경계값·잘림 테스트 목록
5주Worker·FFI·데이터 변환·블로킹 작업web/packages/base/crypto, Rust의 FRB·N-API 바인딩한 분석 요청이 공통 엔진에 도달하는 두 경로
6주Compose·마이그레이션·CI·복구·릴리스server/compose.yaml, .github/workflows/rust-test.yml, CLI 이전 문서필수 운영 데이터 목록과 개발 버전·릴리스 버전 구분표

각 주에 새로운 기능을 하나 더 읽기보다 지난주 그림의 틀린 화살표를 고치는 데 시간을 쓰세요. 폴더 이름을 기억하는 것보다, 입력과 결과가 어느 경계에서 바뀌는지 설명하는 데 집중합니다.

9-1. 목표에 따라 마지막 두 주를 바꿔도 됩니다

이 저장소를 다 읽는 것보다, 한 흐름을 다른 사람에게 소스 근거와 함께 설명할 수 있는 상태가 더 좋은 학습 목표입니다. 예를 들어 “새 사진이 다른 기기에 나타나기까지”를 입력·암호화·저장·동기화·복호화로 나눠 설명해 보세요. 설명이 막히는 경계가 다음에 읽을 곳입니다.

주간 점검
개념·함수·실패 조건을 한 줄씩 연결합니다

“증분 동기화 → _syncCollectionDiff() → 체크포인트 갱신 전후 실패”처럼 적어 보세요. 개념만 쓰면 추상적이고 함수만 쓰면 위치 기록에 그칩니다. 실패 조건을 더하면 설계 의도가 드러납니다.

10핵심 키워드 사전

본문을 읽다가 막히면 돌아올 수 있는 작은 사전
용어쉬운 설명
모노레포여러 앱과 공통 부품을 한 저장소에서 관리하는 방식. 제품과 계층이 함께 나뉩니다.
클라이언트사용자 대신 서버와 통신하는 앱. Ente에서는 복호화와 일부 분석도 수행합니다.
MuseumEnte API 서버의 이름. 계정·권한·용량·데이터 위치와 복제 등의 업무를 맡습니다.
E2EE사용자의 기기에서 암호화하고 허가받은 기기에서 복호화하는 구조입니다.
HTTPS / TLS통신 구간을 보호하는 기술. 서버가 저장 내용을 읽을 수 있는지와는 별도 문제입니다.
평문 / 암호문읽을 수 있는 원래 데이터와 키 없이 내용을 알아보기 어렵게 변환한 데이터입니다.
KEK키를 잠그는 키. 이 문서에서는 비밀번호로부터 유도해 masterKey를 보호하는 키입니다.
masterKey개인 키 계층의 중심인 임의 키. 비밀번호 자체와 같지 않고 암호화된 형태로 보관됩니다.
collectionKey / fileKey앨범 등에 해당하는 묶음의 키와 개별 파일의 키. 파일 키를 컬렉션 키로 감쌉니다.
salt키 유도 입력에 넣는 임의 값. 같은 비밀번호라도 다른 유도 결과를 만들도록 돕습니다.
nonce암호화할 때 사용하는 별도 값. 비밀키와 다르며 해당 알고리즘의 사용 규칙을 지켜야 합니다.
Argon2id메모리와 계산을 소비하도록 설계된 비밀번호 기반 키 유도 방식입니다.
인증 암호화내용을 숨기는 동시에 변조 여부도 검증하는 암호화 방식입니다.
SRP비밀번호 원문을 그대로 보내지 않고 알고 있음을 증명하는 인증 프로토콜 계열. Ente의 구현 세부도 확인해야 합니다.
2FA / TOTP추가 인증 수단과 시간 기반 일회용 코드. 데이터 복호화용 파일 키와 역할이 다릅니다.
서명된 URL서버가 정한 조건과 유효기간 안에서 객체를 읽거나 쓰게 하는 URL. 파일 복호화 키는 아닙니다.
객체 저장소파일을 경로처럼 생긴 키로 보관하는 서비스. 이 저장소는 S3 호환 API를 사용합니다.
repositoryDB에서 데이터를 읽고 쓰는 책임을 모은 코드. Git 저장소라는 의미와 문맥을 구분합니다.
트랜잭션DB 변경을 함께 확정하거나 취소하는 단위. 외부 S3 작업까지 자동으로 포함되지는 않습니다.
증분 동기화 / diff전체 대신 지난 확인 이후 바뀐 부분을 받는 방식과 그 변경 내역입니다.
체크포인트어디까지 변경을 반영했는지 기억하는 위치나 시각입니다.
single-flight같은 작업이 진행 중일 때 그 결과를 공유해 실행이 겹치지 않도록 하는 패턴입니다.
Future / Completer나중에 얻을 결과와 그 결과를 직접 완료하는 도구. Dart 동기화 코드의 핵심입니다.
Worker / isolate주 화면 흐름과 작업을 분리하는 실행 단위. 언어와 런타임에 따라 메모리·통신 규칙이 다릅니다.
FFI / 바인딩다른 언어의 기능을 부르는 경계와 연결 코드. 입력·오류·결과 변환이 필요합니다.
WASM브라우저 등에서 실행할 수 있는 이식 가능한 코드 형식. 이 저장소는 일부 Rust 기능을 여기에 연결합니다.
임베딩이미지나 문장을 특징을 나타내는 숫자 벡터로 바꾼 결과. 검색·비교에 사용합니다.
ONNX RuntimeONNX 형식 모델을 실행하는 엔진. 모델 파일 자체와는 다른 구성 요소입니다.
마이그레이션DB 스키마나 도구 사용 방식을 이전 상태에서 새 상태로 옮기는 절차입니다.
featureRust에서 기능과 의존성 조합을 선택하는 장치. 앱 UI의 기능 버튼과는 문맥이 다릅니다.

11참고 링크

개념 문서에서 시작하되, 판단이 갈리는 곳은 구현으로 돌아옵니다

11-1. 공식 저장소와 문서

11-2. 이 글의 핵심 코드

11-3. 관련 기술을 더 배울 공식 자료

11-4. 현재 상태 (2026-10-23 확인)

항목확인 내용
선정GitHub Trending Dart Daily #1 · 2026-10-23
스타 / 포크29,541 / 1,874 · 2026-10-23 GitHub 기준
주 언어 / 생성일Dart / 2022-11-01
설명💚 End-to-end encrypted cloud for everything. · 2026-10-23 GitHub 기준
최신 릴리스ente-cli-v0.4.0 · 2026-10-23
최근 push2026-10-23 · GitHub 기준
최근 커밋939ee85 · 2026-10-23 · Ente CLI 가이드 공개 문서 변경, PR #13985
개발 중 CLI 버전rust/apps/cli/Cargo.toml: 0.4.1. 최신 릴리스와 구별
코드 규모주요 10개 언어·SQL 합계 약 984,236줄. Git 추적 일반 파일은 전체 8,240개
라이선스루트 LICENSE: AGPL-3.0 (GNU AGPL v3). 개별 의존성·모델의 라이선스는 별도 확인

11-5. 언어별 코드 규모와 집계 범위

커밋 939ee85 기준 Git 추적 일반 파일의 확장자별 물리적 줄 수입니다. 빈 줄·주석·테스트·추적된 생성 코드를 포함하며 Git 내부 데이터·비추적 파일·바이너리 자산은 아래 언어 집계에서 제외합니다. 직접 작성한 구현만의 줄 수나 기능의 복잡도 순위로 읽으면 안 됩니다.

언어·분류파일 수대략적인 물리적 줄 수
Dart1,906399,336
TypeScript1,136246,324 · .ts와 .tsx 합계
Rust499168,836
Go55395,286 · 서버 89,070, 기존 CLI 6,216
Kotlin18427,395
Swift16526,825
JavaScript1228,520 · .js/.jsx/.mjs/.cjs 합계
Python307,148
SQL3063,600
C/C++25966 · .c/.cpp/.h/.hpp 합계

대부분의 Dart는 mobile/에 있고, TypeScript 중 236,783줄은 web/에 있습니다. Rust에는 생성 바인딩도 포함됩니다. 집계는 확장자 기반이므로 HTML·CSS·문서·설정·번역 자산 전체의 합계와는 다릅니다. 누적 커밋 수와 기여자별 기여량을 산정하려면 전체 Git 이력이 필요합니다.

해석할 때 주의할 점
개념 문서·홍보 설명·현재 구현의 범위를 구분해야 합니다

보안: 루트 README는 Cure53·Symbolic Software·Fallible의 외부 감사를 소개합니다. 이 사실이 커밋의 모든 파일이나 이후 모든 변경까지 검증됐다는 뜻은 아닙니다. 정적 코드 분석은 보안 감사나 성능 시험을 대신하지 않습니다.

인증: architecture/README.md의 이메일·암호화 토큰 중심 설명만으로 전체 로그인을 설명할 수 없습니다. rust/crates/accounts/src/login.rs의 LoginFlow는 이메일 코드·비밀번호·추가 인증 상태를 다루며, rust/crates/accounts/src/auth/srp.rs는 SrpSession::compute_m1()과 verify_m2()를 구현합니다.

가용성: README의 3중 데이터 복제는 서비스의 설명입니다. 로컬 Compose의 세 버킷만으로 세 사업자·세 장애 영역의 독립성이 생기지는 않습니다. 실제 배포의 내구성과 장애 복구 시간은 로컬 구성 예제만으로 알 수 없습니다.

현재성: 스타·릴리스·트렌딩은 분석일의 시점 정보이고 온라인 도움말은 바뀔 수 있습니다. 현재 소스의 존재만으로 모든 개발 중 앱·feature가 동일하게 출시됐다고 해석하지 마세요. 성능 수치는 별도 측정이 필요하며, 전체 기여 이력과 경쟁 제품의 정책은 각각 전체 Git 이력과 해당 제품의 공식 자료를 기준으로 판단해야 합니다.

가장 오래 남길 배움은 암호화·저장·동기화의 경계마다 지켜야 할 조건을 찾아내는 읽기 습관입니다. 그 조건을 함수 이름과 함께 설명할 수 있다면, 다른 클라우드 앱을 볼 때도 같은 질문을 던질 수 있습니다.