Contributing
Bu içerik henüz dilinizde mevcut değil.
Contributions are welcome. This guide covers local development setup for both the backend and frontend.
Prerequisites
Section titled “Prerequisites”| Tool | Version | Install |
|---|---|---|
| Rust | 1.91.1+ (stable) | https://rustup.rs/ |
| Node.js | 22 (project and CI job runtime) | https://nodejs.org/ |
| pnpm | 10.28.2 (root package and CI) | corepack enable |
| Docker + Compose v2 | latest | https://docs.docker.com/get-docker/ |
Local Development Setup
Section titled “Local Development Setup”1. Clone and configure
Section titled “1. Clone and configure”git clone https://github.com/kutupbt/kutup.gitcd kutupcp .env.example .env# Fill in required values — see README for the configuration table2. Start infrastructure (database + storage)
Section titled “2. Start infrastructure (database + storage)”The easiest approach is to run the full stack and then replace only the service you’re working on:
docker compose up -d --buildFor faster iteration, you can run just the infrastructure services and run the backend/frontend natively:
docker compose up -d postgres seaweedfs-master seaweedfs-volume seaweedfs-filer seaweedfs-s3 seaweedfs-initBackend Development
Section titled “Backend Development”The backend is a Rust application — crates/kutup-server (Axum + sqlx + aws-sdk-s3) in the
root Cargo workspace. It shares the E2EE primitives with the CLI via crates/kutup-crypto.
Running natively
Section titled “Running natively”# Export env vars (or use a tool like direnv)export DATABASE_URL="postgres://kutup:<POSTGRES_PASSWORD>@localhost:5432/kutup?sslmode=disable"export JWT_SECRET="<your-jwt-secret-32+chars>"export S3_ENDPOINT="http://localhost:8333"export S3_ACCESS_KEY="kutup"export S3_SECRET_KEY="<your-s3-secret>"export S3_BUCKET="kutup-files"export S3_REGION="us-east-1"export APP_ENV="development"
cargo run -p kutup-server # or: cargo build --release -p kutup-serverThe backend starts on http://localhost:3000. The binary also has an orphan-sweep
subcommand (cargo run -p kutup-server -- orphan-sweep [--delete]) for GC’ing orphaned S3
blobs.
You need to expose the SeaweedFS S3 port to the host. Add
ports: ["8333:8333"]to theseaweedfs-s3service indocker-compose.ymltemporarily for local dev.
Database migrations
Section titled “Database migrations”Migrations live in crates/kutup-server/migrations/ (<N>_<name>.up.sql / .down.sql —
sqlx’s reversible format) and are embedded into the binary at compile time via
sqlx::migrate!(), then applied automatically on startup.
To add a migration, create the pair by hand (or with the sqlx CLI):
cargo install sqlx-cli --no-default-features --features postgres # one-timesqlx migrate add -r <migration_name> --source crates/kutup-server/migrationsWrite the forward migration in .up.sql and the rollback in .down.sql. Because migrations
are embedded at compile time, rebuild the server after adding one.
OpenAPI
Section titled “OpenAPI”The server generates its OpenAPI document with utoipa
and serves the machine-readable JSON at GET /api-docs/openapi.json. Every HTTP
operation has a path annotation, and a coverage test keeps the generated route set aligned
with the Axum router. An interactive Swagger UI is deferred (see docs/roadmap.md).
Running tests
Section titled “Running tests”cargo test # all cratescargo test -p kutup-crypto # crypto byte-parity vectorscargo clippy --all-targets -- -D warnings # lints (gate)cargo fmt --check # formatting (gate)python3 scripts/test-check-docs.py # checker regression testspython3 scripts/check-docs.py # Markdown links and referenced pathsdocker run --rm -v "$PWD:/repo" -w /repo \ rhysd/actionlint@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667 \ -no-color # GitHub workflow lint./scripts/audit-unified-federation.sh # no feature-owned federation stack./scripts/test-chat-backup-integration.sh # real Postgres/SeaweedFS backup lifecycle./scripts/test-chat-federation.sh # full two-server API + browser security/recovery gate./scripts/dev-chat-federation-up.sh # leave an MLS-enabled two-server stack runningThe backup integration script owns a disposable Compose project and covers the HTTP lifecycle, fixed-cutoff mailbox/media retention, account purge, object cleanup, and exact Chat-byte release. It tears down its Postgres and SeaweedFS state even on failure.
The federation harness uses its own Compose project, two tmpfs Postgres
databases, and host ports 39081/39082. Despite its historical filename, it is
the unified Chat + Drive gate: Drive establishes the first peer pin, Chat must
reuse that same identity and policy, and the suite checks the shared admin
evidence/retry/audit control plane. It also covers the Drive share lifecycle,
Chat delivery and durable retry, the global emergency stop, all four admission
modes and directional domain rules independently for both features, and
disabled feature capabilities. Its Playwright phase also covers Direct and MLS
security, media, clean two-account browser-loss recovery, lazy protected-media
restore, and fresh post-restore protocol state. It tears the topology down on
exit and does not touch the ordinary development stack. Set KUTUP_FEDERATION_SKIP_BUILD=1
only when reusing an image already built from the current checkout.
Run these local gates before requesting or rerunning GitHub CI. CI is final
confirmation, not the first debugging environment. See
tests/e2e/README.md for the single-server recovery
command, TLS prerequisite, zero-retry policy, and sanitized failure artifacts.
Markdown-only and docs/**-only pull requests/pushes run the lightweight
Documentation workflow instead of the Rust, WASM, frontend, integration, and
browser matrix. It validates local links, referenced script/workflow/spec paths,
changed-file whitespace, and Compose parsing. A mixed documentation-and-code
change still runs the complete CI workflow as well as documentation checks.
Changes confined to .github/workflows/** run the separate lightweight
Workflow validation workflow. It exercises actions/checkout@v7,
pnpm/action-setup@v6, actions/setup-node@v7, and
actions/upload-artifact@v7 with the project’s Node 22 and pnpm 10.28.2, then
runs the digest-pinned actionlint image. These action majors use the Node 24
GitHub Actions runtime; that implementation detail does not change the Node 22
version used to build and test Kutup. Workflow-only changes skip the complete
CI matrix, while a mixed workflow-and-application change still runs it.
For manual browser testing, use ./scripts/dev-chat-federation-up.sh instead
of invoking docker-compose.chat-federation.yml directly. The helper starts
the same real two-server topology, completes the authenticated admin bootstrap,
and changes both Chat federation policies from their fail-closed allowlist
default to open. It is repeatable while its tmpfs databases remain running.
The script prints both URLs and the development-only admin credentials. Override
KUTUP_FEDERATION_PROJECT, KUTUP_FED_A_PORT, and KUTUP_FED_B_PORT to run an
additional isolated topology.
The intentionally breaking Chat and Drive cutovers have separate up/down
isolation fixtures. Point KUTUP_TEST_DB at a disposable PostgreSQL database;
each test creates and removes its own randomized schema while proving local
product rows survive:
KUTUP_TEST_DB=postgres://... cargo test -p kutup-server \ --test federation_phase_c_migration_live -- --nocaptureKUTUP_TEST_DB=postgres://... cargo test -p kutup-server \ --test federation_phase_d_migration_live -- --nocaptureFrontend Development
Section titled “Frontend Development”The frontend is a React 18 + TypeScript app in frontend/, built with Vite.
Running natively
Section titled “Running natively”cd frontendpnpm installpnpm devVite starts on http://localhost:5173. The vite.config.ts includes a proxy rule that forwards /api requests to the backend at http://localhost:3000, so you can develop against a running backend without CORS issues.
Building for production
Section titled “Building for production”pnpm buildOutput goes to frontend/dist/, which is then served by the frontend Nginx container.
TypeScript
Section titled “TypeScript”The project uses strict TypeScript ("strict": true in tsconfig.json). All new code must type-check cleanly. Run the type checker:
pnpm tsc --noEmitProject Structure
Section titled “Project Structure”kutup/├── Cargo.toml # Root Cargo workspace (backend + CLI + crypto)├── Dockerfile.server # Build image for the Rust kutup-server├── crates/│ ├── kutup-server/ # Backend API (Axum + sqlx + aws-sdk-s3)│ │ ├── src/main.rs # Server setup, route registration, layers, subcommands│ │ ├── src/handlers/ # HTTP handlers (one file per domain)│ │ ├── src/{jwt,totp,ssrf,ratelimit,middleware}.rs # auth, rate limiting, SSRF guard│ │ ├── src/{storage,jobs,hub}.rs # S3 client, background jobs, collab room hub│ │ ├── src/{models,error,config,db,openapi}.rs│ │ └── migrations/ # SQL migrations (embedded via sqlx::migrate!())│ ├── kutup-cli/ # The `kutup` CLI (clap)│ │ └── src/{commands,api,session,syncengine,transfer}/ # commands, HTTP client, session store, sync│ ├── kutup-crypto/ # Canonical Kutup-owned E2EE formats│ │ ├── src/{kdf,account_envelope,drive_envelope,drive_object,named_share,stream,envelope}.rs│ │ └── tests/vectors/ # Checked-in byte-parity vectors│ ├── kutup-crypto-wasm/ # Browser bindings for canonical crypto│ ├── kutup-chat-proto/ # Typed Chat wire DTOs and validation│ ├── kutup-federation-proto/ # Shared authenticated federation types│ ├── kutup-chat-core/ # Standalone libsignal/OpenMLS engine; native + WASM│ └── kutup-client-ffi/ # Standalone UniFFI Swift/Kotlin boundary├── frontend/│ ├── src/│ │ ├── api/client.ts # Axios instance with auth interceptors│ │ ├── crypto/ # Thin Rust/WASM format adapters + primitive-only stream adapter│ │ ├── collab/ # Drive editor envelope and WebSocket layer│ │ ├── chat/ # Direct/MLS services, backup, media, IndexedDB state│ │ ├── components/editors/│ │ │ ├── TextCollabEditor.tsx # Notes / code (CodeMirror 6 + Yjs)│ │ │ ├── office/OfficeEditor.tsx # .docx/.xlsx/.pptx (OnlyOffice bridge)│ │ │ └── whiteboard/WhiteboardEditor.tsx # .excalidraw (Excalidraw + last-write-wins)│ │ ├── pages/ # Drive, Chat, editor, settings, admin, auth routes│ │ ├── store/ # Redux slices (auth state)│ │ └── workers/ # Web Worker for Rust/WASM Argon2id KDF│ ├── public/onlyoffice/ # Bridge source; verified assets overlay during Docker builds│ └── vite.config.ts # Dev server proxy config├── src-tauri/ # Tauri desktop shell; experimental mobile targets remain available│ ├── src/lib.rs # Plugin setup + OS-keychain vault commands (vault_set/get/delete)│ ├── tauri.conf.json # Bundle id (dev.kutup.client), mainBinaryName (kutup-client), targets, scopes│ └── capabilities/ # Tauri permission capabilities (default.json + desktop.json)├── nginx/nginx.conf # Production Nginx config├── docs/ # Start at docs/README.md├── tests/e2e/ # Single- and two-server Playwright gates├── fuzz/ # Standalone Chat/security cargo-fuzz package└── docker-compose.ymlOperations
Section titled “Operations”Orphan-blob sweep
Section titled “Orphan-blob sweep”Periodic admin task that walks SeaweedFS for blobs whose containing files.id row no longer exists (PUT-then-crash leftovers, residual snapshot blobs from before quota tracking, etc.) and deletes them.
Subcommand on the existing kutup-server binary — same Docker image, same env vars, same DB pool.
Always start with a dry-run. Default behaviour reports orphans without touching them.
# Dry-run (default). Lists orphans + summary; no deletions.docker compose exec backend ./kutup-server orphan-sweep
# Tighter age window for testing — anything older than 1h is fair game.docker compose exec backend ./kutup-server orphan-sweep --age-floor=1h
# After verifying the dry-run output looks right, actually delete.docker compose exec backend ./kutup-server orphan-sweep --deleteFlags:
| Flag | Default | Notes |
|---|---|---|
--delete |
false |
Without this, the command is a dry-run. |
--age-floor |
24h |
Skip blobs younger than this. The 24h default absorbs in-flight uploads; lower it only for testing. |
--page-sleep |
200ms |
Sleep between S3 LIST pages. |
--prefix |
files/ |
S3 key prefix to walk. |
Reading the summary log:
orphan-sweep summary: pages=N keys=N orphans=N skipped-age=N skipped-shape=N deleted=N bytes-reclaimed=N mode=dry-run|deleteskipped-ageshould be > 0 on a healthy bucket (the in-flight upload window). If it’s 0 every run, the age floor isn’t engaging — investigate before relying on the result.skipped-shapecounts keys outside thefiles/<UUID>/...shape; the sweep never deletes these.bytes-reclaimedis the projected (dry-run) or actual (--delete) byte savings.
The sweep does not persist progress — a crash mid-run means rerunning from scratch. Acceptable at current scale; revisit if the bucket grows past ~500K objects.
Code Conventions
Section titled “Code Conventions”Backend (Rust)
Section titled “Backend (Rust)”- Axum handlers organized by domain (
crates/kutup-server/src/handlers/— auth, collections, files, shares, federation, admin, …). Each file opens with a//!doc comment. - Use
sqlxruntime queries (sqlx::query/query_as) — no compile-time-checked macros (no live DB at build), no ORM. - All cryptographic operations are the client’s responsibility; the backend must never attempt to decrypt anything. Shared primitives live in
crates/kutup-crypto. - SSRF validation (
crates/kutup-server/src/ssrf.rs) must be applied to all user-supplied URLs before making outbound requests (federation). - Gate every change with
cargo clippy --all-targets -- -D warnings+cargo fmt+cargo test.
Frontend (TypeScript)
Section titled “Frontend (TypeScript)”- Strict mode is enforced. No
anytypes. - Authored frontend files use
.tsand.tsxonly; do not add.jsor.jsx. - All cryptographic operations go in
src/crypto/. Components and pages must not call libsodium directly. - KDF (Argon2id) runs in
src/workers/kdf.worker.tsto avoid blocking the main thread. - State management uses Redux Toolkit slices. Keep slices thin — business logic goes in thunks or service functions.
- API calls go through
src/api/client.ts, which handles token injection and refresh. - Use semantic theme roles and the responsive ownership boundaries documented
in
frontend.md. A viewport change must not remount services or trigger a mutation.
Submitting Changes
Section titled “Submitting Changes”- Fork the repository and create a feature branch from
master. - Make focused, well-described commits. Each commit should be buildable and leave tests passing.
- Open a pull request against
master. Describe why the change is needed, not just what it does. - For security-related changes (cryptography, authentication, federation), include a brief explanation of the security model impact.
For bug reports and feature requests, open a GitHub issue.