mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(docs-i18n): translate all documentation into 20 languages
All 181 docs markdown files translated into 20 languages (apps/docs/<locale>/**). Companion to the i18n code PR; admin-merged because the file count exceeds GitHub's per-PR CI trigger limit. Validated by pnpm i18n:check (all surfaces, 0 stale/missing) and a clean all-locale docs build.
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
---
|
||||
description: "Struktur monorepo, arsitektur aplikasi dan paket, siklus hidup permintaan, dan jejak sumber daya SnapOtter."
|
||||
i18n_source_hash: 9e8f80499a37
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: d4bd8ceef301
|
||||
---
|
||||
|
||||
# Arsitektur {#architecture}
|
||||
|
||||
SnapOtter adalah monorepo yang dikelola dengan workspace pnpm dan Turborepo. Ia di-deploy sebagai stack Docker Compose 3 kontainer: image aplikasi SnapOtter, PostgreSQL 17, dan Redis 8.
|
||||
|
||||
## Struktur proyek {#project-structure}
|
||||
|
||||
```
|
||||
snapotter/
|
||||
├── apps/
|
||||
│ ├── api/ # Fastify backend
|
||||
│ ├── web/ # React + Vite frontend
|
||||
│ └── docs/ # This VitePress site
|
||||
├── packages/
|
||||
│ ├── image-engine/ # Sharp-based image operations
|
||||
│ ├── media-engine/ # FFmpeg spawn + progress parsing
|
||||
│ ├── doc-engine/ # qpdf, LibreOffice, ghostscript wrappers
|
||||
│ ├── ai/ # Python AI model bridge
|
||||
│ └── shared/ # Types, constants, i18n
|
||||
└── docker/ # Dockerfile and Compose config
|
||||
```
|
||||
|
||||
## Paket {#packages}
|
||||
|
||||
### `@snapotter/image-engine` {#snapotter-image-engine}
|
||||
|
||||
Pustaka pemrosesan gambar inti yang dibangun di atas [Sharp](https://sharp.pixelplumbing.com/). Ia menangani semua operasi non-AI: resize, crop, rotate, flip, convert, compress, strip metadata, dan penyesuaian warna (kecerahan, kontras, saturasi, grayscale, sepia, invert, saluran warna).
|
||||
|
||||
Paket ini tidak memiliki dependensi jaringan dan berjalan sepenuhnya dalam proses.
|
||||
|
||||
### `@snapotter/ai` {#snapotter-ai}
|
||||
|
||||
Lapisan penghubung yang memanggil skrip Python untuk operasi ML. Pada penggunaan pertama, penghubung memulai proses dispatcher Python yang persisten yang mengimpor terlebih dahulu pustaka berat (PIL, NumPy, MediaPipe, rembg) sehingga panggilan AI berikutnya melewati overhead impor. Jika dispatcher belum siap, penghubung mundur ke pemunculan subproses Python baru per permintaan.
|
||||
|
||||
**Model tidak dimuat terlebih dahulu.** Setiap skrip tool memuat bobot modelnya dari disk pada waktu permintaan dan membuangnya saat permintaan selesai. Lihat [Jejak sumber daya](#resource-footprint) untuk profil memori lengkap.
|
||||
|
||||
Operasi yang didukung: penghapusan latar belakang (rembg/BiRefNet), upscaling (RealESRGAN), blur wajah (MediaPipe), penyempurnaan wajah (GFPGAN/CodeFormer), penghapusan objek (LaMa ONNX), OCR (PaddleOCR/Tesseract), pewarnaan (DDColor), penghapusan derau, penghapusan mata merah, restorasi foto, pembuatan foto paspor, pemperbaiki transparansi (matting HR BiRefNet), dan pengubahan ukuran sadar-konten (biner Go caire).
|
||||
|
||||
Skrip Python berada di `packages/ai/python/`. Image Docker mengunduh terlebih dahulu semua bobot model selama build sehingga kontainer bekerja sepenuhnya offline.
|
||||
|
||||
### `@snapotter/shared` {#snapotter-shared}
|
||||
|
||||
Tipe TypeScript bersama, konstanta (seperti `APP_VERSION` dan definisi tool), dan string terjemahan i18n yang digunakan oleh frontend dan backend.
|
||||
|
||||
## Aplikasi {#applications}
|
||||
|
||||
### API (`apps/api`) {#api-apps-api}
|
||||
|
||||
Server Fastify v5 yang mengekspos 241 route tool di lima modalitas (image, video, audio, PDF, file) yang menangani:
|
||||
- Unggahan file, manajemen workspace sementara, dan penyimpanan file persisten
|
||||
- Pustaka file pengguna dengan rantai versi (tabel `user_files`) - setiap hasil yang diproses tertaut kembali ke file sumbernya dan mencatat tool mana yang diterapkan, dengan thumbnail yang dibuat otomatis untuk halaman Files
|
||||
- Eksekusi tool (mengarahkan setiap permintaan tool ke image engine atau AI bridge)
|
||||
- Orkestrasi pipeline (merangkai beberapa tool secara berurutan)
|
||||
- Pemrosesan batch dengan kontrol konkurensi melalui antrean job BullMQ (pool: image, media, ai, docs, system)
|
||||
- Autentikasi pengguna, RBAC (peran admin/user dengan set izin lengkap), manajemen kunci API, dan pembatasan laju
|
||||
- Manajemen Teams - CRUD hanya-admin; pengguna ditugaskan ke sebuah tim melalui field `team` di profil mereka
|
||||
- Pengaturan runtime - penyimpanan key-value di tabel `settings` yang mengontrol `disabledTools`, `enableExperimentalTools`, `loginAttemptLimit`, dan tombol operasional lainnya tanpa deploy ulang
|
||||
- Branding kustom dan preferensi runtime melalui pengaturan yang didukung basis data
|
||||
- Dokumentasi Scalar/OpenAPI di `/api/docs`
|
||||
- Menyajikan frontend yang telah dibangun sebagai SPA dalam produksi
|
||||
|
||||
Dependensi utama: Fastify, Drizzle ORM (pg-core, node-postgres), Sharp, BullMQ, ioredis, Zod untuk validasi.
|
||||
|
||||
Server menangani penghentian yang mulus pada SIGTERM/SIGINT: ia menguras koneksi HTTP, menghentikan worker BullMQ, mematikan dispatcher Python, dan menutup koneksi basis data.
|
||||
|
||||
### Web (`apps/web`) {#web-apps-web}
|
||||
|
||||
Aplikasi single-page React 19 yang dibangun dengan Vite. Menggunakan Zustand untuk manajemen state, Tailwind CSS v4 untuk penataan, dan Lucide untuk ikon. Berkomunikasi dengan API melalui REST dan SSE (untuk pelacakan progres).
|
||||
|
||||
Halaman mencakup workspace tool, halaman Files untuk mengelola unggahan dan hasil persisten, pembuat automasi/pipeline, dan panel pengaturan admin.
|
||||
|
||||
Frontend yang telah dibangun disajikan oleh backend Fastify dalam produksi, jadi tidak ada server web terpisah di kontainer Docker.
|
||||
|
||||
### Docs (`apps/docs`) {#docs-apps-docs}
|
||||
|
||||
Situs VitePress ini. Di-deploy ke Cloudflare Pages secara otomatis saat push ke `main`.
|
||||
|
||||
## Bagaimana sebuah permintaan mengalir {#how-a-request-flows}
|
||||
|
||||
1. Pengguna memilih sebuah tool di UI web dan mengunggah file.
|
||||
2. Frontend mengirim POST multipart ke `/api/v1/tools/:section/:toolId` dengan file dan pengaturan.
|
||||
3. Route API memvalidasi input dengan Zod, lalu mengirim pemrosesan.
|
||||
4. Untuk tool standar, job dimasukkan ke antrean ke pool BullMQ yang sesuai (image, media, atau docs berdasarkan modalitas). Worker BullMQ dalam proses secara otomatis mengorientasikan gambar berdasarkan metadata EXIF, menjalankan fungsi proses tool, dan mengembalikan hasilnya.
|
||||
5. Untuk tool AI, penghubung TypeScript mengirim permintaan ke dispatcher Python yang persisten (atau memunculkan subproses baru sebagai fallback), menunggunya selesai, dan membaca file keluaran.
|
||||
6. Progres job dipertahankan ke tabel `jobs` di PostgreSQL sehingga state bertahan saat kontainer dimulai ulang. Pembaruan waktu nyata dikirimkan melalui SSE di `/api/v1/jobs/:jobId/progress`.
|
||||
7. API mengembalikan `jobId` dan `downloadUrl`. Pengguna mengunduh file yang telah diproses dari `/api/v1/download/:jobId/:filename`.
|
||||
|
||||
Untuk pipeline, API memberi output setiap langkah sebagai input ke langkah berikutnya, menjalankannya secara berurutan.
|
||||
|
||||
Untuk pemrosesan batch, API menggunakan flow BullMQ dengan child job per langkah dan mengembalikan file ZIP berisi semua file yang diproses.
|
||||
|
||||
## Jejak sumber daya {#resource-footprint}
|
||||
|
||||
SnapOtter dirancang untuk penggunaan memori idle yang rendah. Tidak ada yang dimuat terlebih dahulu atau dijaga tetap hangat saat startup.
|
||||
|
||||
### Saat idle {#at-idle}
|
||||
|
||||
Proses Node.js/Fastify, PostgreSQL, dan Redis berjalan. RAM idle tipikal adalah **~200-300 MB** di ketiga kontainer (proses Node.js, Postgres, dan Redis). Tidak ada proses Python, tidak ada bobot model di memori.
|
||||
|
||||
### Apa yang mulai, dan kapan {#what-starts-and-when}
|
||||
|
||||
| Komponen | Mulai saat | Memori saat aktif |
|
||||
|-----------|-------------|---------------------|
|
||||
| Server Fastify + Postgres + Redis | Kontainer mulai | ~200-300 MB total |
|
||||
| Worker BullMQ | Kontainer mulai (dalam proses) | Satu worker per pool (image, media, ai, docs, system) |
|
||||
| Dispatcher Python | Permintaan tool AI pertama | Interpreter Python + pustaka yang diimpor terlebih dahulu (PIL, NumPy, MediaPipe, rembg) - tanpa bobot model |
|
||||
| Bobot model AI | Selama permintaan tool spesifik | Dimuat dari disk, dibebaskan saat permintaan selesai |
|
||||
|
||||
### Pemuatan model {#model-loading}
|
||||
|
||||
Semua file bobot model (berjumlah beberapa GB) berada di disk di `/opt/models/` setiap saat. Setiap skrip tool AI hanya memuat model miliknya sendiri ke memori selama durasi permintaan, lalu melepaskannya. Beberapa skrip secara eksplisit memanggil `del model` dan `torch.cuda.empty_cache()` setelah inferensi untuk memastikan memori segera dikembalikan.
|
||||
|
||||
Tidak ada cache model antar permintaan. Menjalankan tool AI yang sama berturut-turut memuat ulang model setiap kali. Ini menjaga memori idle mendekati nol dengan biaya penundaan pemuatan model pada setiap permintaan AI.
|
||||
|
||||
### Cold start permintaan AI pertama {#first-ai-request-cold-start}
|
||||
|
||||
Dispatcher Python tidak berjalan saat kontainer dimulai. Permintaan AI pertama memicu dua hal secara paralel: dispatcher mulai memanas di latar belakang, dan permintaan itu sendiri mundur ke pemunculan subproses Python sekali pakai. Setelah dispatcher menandakan siap, semua permintaan AI berikutnya menggunakannya secara langsung dan melewati biaya pemunculan subproses.
|
||||
@@ -0,0 +1,164 @@
|
||||
---
|
||||
description: "Semua variabel lingkungan SnapOtter dengan default-nya. Konfigurasi auth, penyimpanan, model AI, analitik, dan lainnya."
|
||||
i18n_source_hash: 8e9e9ca2840c
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 48af4392804f
|
||||
---
|
||||
|
||||
# Konfigurasi {#configuration}
|
||||
|
||||
Semua konfigurasi dilakukan melalui variabel lingkungan. Setiap variabel memiliki default yang masuk akal, jadi SnapOtter bekerja langsung tanpa mengatur satu pun dari mereka.
|
||||
|
||||
## Variabel lingkungan {#environment-variables}
|
||||
|
||||
### Server {#server}
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|---|---|---|
|
||||
| `PORT` | `1349` | Port tempat server mendengarkan. |
|
||||
| `RATE_LIMIT_PER_MIN` | `1000` | Permintaan maksimum per menit per IP. Setel ke 0 untuk menonaktifkan pembatasan laju. |
|
||||
| `CORS_ORIGIN` | (kosong) | Origin yang diizinkan dipisahkan koma untuk CORS, atau kosong untuk hanya same-origin. |
|
||||
| `LOG_LEVEL` | `info` | Verbositas log. Salah satu dari: `fatal`, `error`, `warn`, `info`, `debug`, `trace`. |
|
||||
| `TRUST_PROXY` | `true` | Percayai header `X-Forwarded-For` dari reverse proxy. Setel ke `false` jika tidak di belakang proxy. |
|
||||
|
||||
### Autentikasi {#authentication}
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|---|---|---|
|
||||
| `AUTH_ENABLED` | `false` | Setel ke `true` untuk mewajibkan login. Image Docker default ke `true`. |
|
||||
| `DEFAULT_USERNAME` | `admin` | Nama pengguna untuk akun admin awal. Hanya digunakan pada run pertama. |
|
||||
| `DEFAULT_PASSWORD` | `admin` | Kata sandi untuk akun admin awal. Ubah ini setelah login pertama. |
|
||||
| `MAX_USERS` | `0` (tak terbatas) | Jumlah maksimum akun pengguna terdaftar. Setel ke 0 untuk tak terbatas. |
|
||||
| `SESSION_DURATION_HOURS` | `168` | Masa hidup sesi login dalam jam (default adalah 7 hari). |
|
||||
| `SKIP_MUST_CHANGE_PASSWORD` | - | Setel ke nilai apa pun yang tidak kosong untuk melewati prompt ganti-kata-sandi paksa pada login pertama |
|
||||
|
||||
### Penyimpanan {#storage}
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|---|---|---|
|
||||
| `STORAGE_MODE` | `local` | `local` atau `s3`. S3/MinIO memerlukan lisensi dengan fitur s3_storage. |
|
||||
| `DATABASE_URL` | `postgres://snapotter:snapotter@postgres:5432/snapotter` | String koneksi PostgreSQL. |
|
||||
| `REDIS_URL` | `redis://redis:6379` | String koneksi Redis (digunakan untuk antrean job BullMQ). |
|
||||
| `WORKSPACE_PATH` | `./tmp/workspace` | Direktori untuk file sementara selama pemrosesan. Dibersihkan secara otomatis. |
|
||||
| `FILES_STORAGE_PATH` | `./data/files` | Direktori untuk file pengguna persisten (gambar yang diunggah, hasil yang disimpan). |
|
||||
|
||||
### Mode tertanam {#embedded-mode}
|
||||
|
||||
Jalankan image tanpa `DATABASE_URL` dan tanpa `REDIS_URL` dan ia memulai PostgreSQL 17 dan Redis-nya sendiri di dalam kontainer, terikat ke loopback, dengan semua data pada volume `/data`. Ini memulihkan pengalaman `docker run` satu perintah untuk quick start, homelab, dan upgrade dari 1.x. Ini adalah jalur kenyamanan, bukan deployment produksi: untuk produksi, jalankan stack Compose 3 kontainer dengan PostgreSQL dan Redis terpisah. Mode tertanam memerlukan menjalankan kontainer sebagai root dan tidak kompatibel dengan runtime UID arbitrer (OpenShift, Kubernetes `runAsNonRoot`); gunakan Compose di sana.
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|---|---|---|
|
||||
| `EMBEDDED` | `auto` | Diaktifkan otomatis saat `DATABASE_URL` dan `REDIS_URL` keduanya tidak diatur. Setel ke `0` untuk menonaktifkannya (aplikasi kemudian gagal cepat jika tidak ada `DATABASE_URL`/`REDIS_URL` eksternal yang diatur, alih-alih diam-diam memulai basis data dalam kontainer). |
|
||||
| `REDIS_MAXMEMORY` | `512mb` | Batas memori untuk Redis tertanam (hanya mode tertanam). Turunkan pada host dengan memori terbatas seperti Raspberry Pi. |
|
||||
|
||||
Upgrade dari 1.x: letakkan `snapotter.db` lama Anda di `/data/snapotter.db` dalam volume dan mode tertanam mengimpornya ke PostgreSQL tertanam pada boot pertama. Impor berjalan sekali; boot berikutnya melewatinya.
|
||||
|
||||
Catatan telemetri: mode tertanam mewarisi default analitik image seperti konfigurasi lainnya. Image yang diterbitkan dikirim dengan analitik aktif; build dengan `--build-arg SNAPOTTER_ANALYTICS=off`, atau gunakan opt-out admin dalam aplikasi, untuk menonaktifkannya.
|
||||
|
||||
### Batas pemrosesan {#processing-limits}
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|---|---|---|
|
||||
| `MAX_UPLOAD_SIZE_MB` | `100` | Ukuran file maksimum per unggahan dalam megabyte. Setel ke 0 untuk tak terbatas. |
|
||||
| `MAX_BATCH_SIZE` | `100` | Jumlah maksimum file dalam satu permintaan batch. Setel ke 0 untuk tak terbatas. |
|
||||
| `CONCURRENT_JOBS` | `0` (otomatis) | Jumlah job batch yang berjalan secara paralel. Setel ke 0 untuk deteksi otomatis berdasarkan inti CPU yang tersedia. |
|
||||
| `MAX_MEGAPIXELS` | `0` (tak terbatas) | Resolusi gambar maksimum yang diizinkan dalam megapiksel. Setel ke 0 untuk tak terbatas. |
|
||||
| `MAX_WORKER_THREADS` | `0` (otomatis) | Thread worker maksimum untuk pemrosesan gambar. Setel ke 0 untuk deteksi otomatis berdasarkan inti CPU yang tersedia. |
|
||||
| `PROCESSING_TIMEOUT_S` | `0` (tanpa batas) | Waktu pemrosesan maksimum per permintaan dalam detik. Setel ke 0 untuk tanpa timeout. |
|
||||
| `MAX_PIPELINE_STEPS` | `20` | Jumlah maksimum langkah dalam sebuah pipeline. Setel ke 0 untuk tanpa batas. |
|
||||
| `MAX_CANVAS_PIXELS` | `0` (tanpa batas) | Ukuran kanvas maksimum dalam piksel untuk gambar keluaran. Setel ke 0 untuk tanpa batas. |
|
||||
| `MAX_SVG_SIZE_MB` | `0` (tak terbatas) | Ukuran file SVG maksimum dalam megabyte. Setel ke 0 untuk tak terbatas. |
|
||||
| `MAX_SPLIT_GRID` | `100` | Dimensi kisi maksimum untuk tool split gambar. |
|
||||
| `MAX_PDF_PAGES` | `0` (tak terbatas) | Jumlah maksimum halaman PDF untuk konversi PDF-ke-image. Setel ke 0 untuk tak terbatas. |
|
||||
|
||||
### Pembersihan {#cleanup}
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|---|---|---|
|
||||
| `FILE_MAX_AGE_HOURS` | `72` | Berapa lama hasil pemrosesan yang tidak disimpan (unggahan mentah dan keluaran tool) disimpan sebelum penghapusan otomatis. File yang Anda simpan secara eksplisit ke pustaka Files tidak terpengaruh dan bertahan hingga Anda menghapusnya. |
|
||||
| `CLEANUP_INTERVAL_MINUTES` | `60` | Seberapa sering job pembersihan berjalan. |
|
||||
|
||||
### Tampilan {#appearance}
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|---|---|---|
|
||||
| `DEFAULT_THEME` | `light` | Tema default untuk sesi baru. `light` atau `dark`. |
|
||||
| `DEFAULT_LOCALE` | `en` | Bahasa antarmuka default. |
|
||||
| `DEFAULT_TOOL_VIEW` | `sidebar` | Tata letak tool default. `sidebar` atau `fullscreen`. |
|
||||
|
||||
### Izin Docker {#docker-permissions}
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|---|---|---|
|
||||
| `PUID` | `999` | Jalankan proses kontainer sebagai UID ini. Setel agar cocok dengan pengguna host Anda untuk bind mount (`id -u`). |
|
||||
| `PGID` | `999` | Jalankan proses kontainer sebagai GID ini. Setel agar cocok dengan grup host Anda untuk bind mount (`id -g`). |
|
||||
|
||||
## Contoh Docker {#docker-example}
|
||||
|
||||
```yaml
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest
|
||||
ports:
|
||||
- "1349:1349"
|
||||
volumes:
|
||||
- SnapOtter-data:/data
|
||||
- SnapOtter-workspace:/tmp/workspace
|
||||
environment:
|
||||
- AUTH_ENABLED=true
|
||||
- DEFAULT_USERNAME=admin
|
||||
- DEFAULT_PASSWORD=changeme
|
||||
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- MAX_UPLOAD_SIZE_MB=200
|
||||
- CONCURRENT_JOBS=4
|
||||
- FILE_MAX_AGE_HOURS=12
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
redis:
|
||||
image: redis:8-alpine
|
||||
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- SnapOtter-redisdata:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
volumes:
|
||||
SnapOtter-data:
|
||||
SnapOtter-workspace:
|
||||
SnapOtter-pgdata:
|
||||
SnapOtter-redisdata:
|
||||
```
|
||||
|
||||
## Volume {#volumes}
|
||||
|
||||
Stack Docker Compose menggunakan empat volume:
|
||||
|
||||
- `/data` (app) - Model AI, venv Python, dan file pengguna. Pasang ini untuk menyimpan file yang diunggah dan bundel AI yang terpasang di seluruh restart.
|
||||
- `/tmp/workspace` (app) - Penyimpanan sementara untuk file yang sedang diproses. Ini bisa efemeral, tetapi memasangnya menghindari pengisian lapisan writable kontainer.
|
||||
- `SnapOtter-pgdata` (postgres) - Direktori data PostgreSQL. Ini menyimpan semua data relasional (pengguna, pengaturan, pipeline, job, log audit). Cadangkan melalui `pg_dump` atau snapshot volume.
|
||||
- `SnapOtter-redisdata` (redis) - File append-only Redis untuk antrean job yang tahan lama.
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
description: "Cara berkontribusi ke SnapOtter. Laporan bug, permintaan fitur, pull request, dan persyaratan CLA."
|
||||
i18n_source_hash: 528802503035
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: bf536bb687ec
|
||||
---
|
||||
|
||||
# Berkontribusi {#contributing}
|
||||
|
||||
Terima kasih atas minat Anda untuk berkontribusi. Panduan ini menjelaskan cara berpartisipasi, apa yang kami terima, dan cara memulai.
|
||||
|
||||
## Cara berkontribusi {#ways-to-contribute}
|
||||
|
||||
### Issue (tanpa penyiapan) {#issues-no-setup-required}
|
||||
|
||||
- **Laporan bug** - Ada yang rusak? Buka [laporan bug](https://github.com/snapotter-hq/snapotter/issues/new?template=bug_report.yml) dengan langkah-langkah reproduksi.
|
||||
- **Permintaan fitur** - Punya ide? Mulai sebuah [diskusi](https://github.com/snapotter-hq/snapotter/discussions/new?category=ideas) agar komunitas dapat menimbang dan mendukungnya.
|
||||
- **Masalah terjemahan** - Menemukan terjemahan yang salah atau hilang? Buka [issue terjemahan](https://github.com/snapotter-hq/snapotter/issues/new?template=translation.yml).
|
||||
- **Masalah dokumentasi** - Ada yang keliru di dokumentasi? Buka [issue dokumentasi](https://github.com/snapotter-hq/snapotter/issues/new?template=documentation.yml).
|
||||
|
||||
### Kode (memerlukan CLA) {#code-requires-cla}
|
||||
|
||||
Kami menerima pull request untuk:
|
||||
|
||||
| Tipe | Proses |
|
||||
|------|---------|
|
||||
| Perbaikan bug | Buka PR langsung (tautkan issue jika ada) |
|
||||
| Terjemahan baru | Buka PR langsung (lihat [Panduan Terjemahan](/id/guide/translations)) |
|
||||
| Peningkatan dokumentasi | Buka PR langsung |
|
||||
| Peningkatan cakupan tes | Buka PR langsung |
|
||||
| Tool atau fitur baru | Mulai sebuah [diskusi](https://github.com/snapotter-hq/snapotter/discussions/new?category=ideas) terlebih dahulu; seorang maintainer mengubah ide yang disetujui menjadi issue yang dilacak sebelum Anda menulis kode |
|
||||
| Refaktor atau perubahan arsitektur | Mulai sebuah [diskusi](https://github.com/snapotter-hq/snapotter/discussions/new?category=ideas) terlebih dahulu dan tunggu persetujuan maintainer sebelum menulis kode |
|
||||
|
||||
### Apa yang tidak akan kami terima {#what-we-will-not-accept}
|
||||
|
||||
- Perubahan pada alur kerja CI/CD, konfigurasi rilis, atau konfigurasi linter/compiler
|
||||
- PR tanpa [Contributor License Agreement](#contributor-license-agreement) yang ditandatangani
|
||||
- PR dengan lebih dari 400 baris perubahan (pecah pekerjaan besar menjadi PR yang lebih kecil)
|
||||
- Fitur yang tidak didiskusikan dan disetujui terlebih dahulu
|
||||
- Perubahan pada `packages/ai/` tanpa diskusi sebelumnya
|
||||
|
||||
## Contributor License Agreement {#contributor-license-agreement}
|
||||
|
||||
Sebelum kami dapat menggabungkan PR pertama Anda, Anda harus menandatangani [Individual CLA](https://github.com/snapotter-hq/snapotter/blob/main/CLA.md) kami. Ini adalah persyaratan satu kali.
|
||||
|
||||
**Mengapa:** SnapOtter berlisensi ganda (AGPLv3 + komersial). CLA memberi kami hak untuk mendistribusikan kontribusi Anda di bawah kedua lisensi tersebut. Anda tetap memegang penuh hak cipta atas karya Anda.
|
||||
|
||||
**Bagaimana:** Ketika Anda membuka PR pertama, bot CLA Assistant akan berkomentar dengan sebuah tautan. Klik tautan itu, tinjau perjanjiannya, dan tanda tangani dengan akun GitHub Anda. Hanya butuh 30 detik.
|
||||
|
||||
Jika Anda berkontribusi atas nama pemberi kerja Anda dan pemberi kerja Anda memegang hak kekayaan intelektual atas karya Anda, hubungi contact@snapotter.com untuk mengatur Corporate CLA sebelum mengirimkan.
|
||||
|
||||
## Memulai {#getting-started}
|
||||
|
||||
### Prasyarat {#prerequisites}
|
||||
|
||||
- Node.js 22+
|
||||
- pnpm 9+
|
||||
- Python 3.11+ (hanya untuk tool AI)
|
||||
- Docker (opsional, untuk pengujian integrasi penuh)
|
||||
|
||||
### Penyiapan {#setup}
|
||||
|
||||
```bash
|
||||
# Fork and clone
|
||||
git clone https://github.com/<your-username>/snapotter.git
|
||||
cd snapotter
|
||||
|
||||
# Start Postgres + Redis for local dev
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Start dev servers (web on :1349, API on :13490)
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Menjalankan pemeriksaan {#running-checks}
|
||||
|
||||
Sebelum mengirimkan PR, pastikan semua pemeriksaan lolos secara lokal:
|
||||
|
||||
```bash
|
||||
pnpm lint # Biome lint + format check
|
||||
pnpm typecheck # TypeScript across monorepo
|
||||
pnpm test # Vitest unit + integration tests
|
||||
```
|
||||
|
||||
## Proses pull request {#pull-request-process}
|
||||
|
||||
1. Fork repo dan buat branch dari `main` (`feat/my-feature` atau `fix/issue-123`)
|
||||
2. Lakukan perubahan Anda dalam commit yang terfokus dan dapat ditinjau menggunakan [conventional commits](https://www.conventionalcommits.org/)
|
||||
3. Tambahkan atau perbarui tes untuk perubahan Anda
|
||||
4. Jalankan `pnpm lint && pnpm typecheck && pnpm test` secara lokal
|
||||
5. Buka PR terhadap `main` dan isi templatnya
|
||||
6. Tanda tangani CLA jika diminta
|
||||
7. Tunggu CI lolos dan seorang maintainer meninjau
|
||||
|
||||
### Ekspektasi peninjauan {#review-expectations}
|
||||
|
||||
- Kami berupaya menanggapi PR dalam 7 hari
|
||||
- PR yang kecil dan terfokus ditinjau lebih cepat
|
||||
- Jika Anda belum mendapat kabar dalam 7 hari, tinggalkan komentar untuk mengingatkan thread
|
||||
- Kami mungkin meminta perubahan, menyarankan pendekatan yang berbeda, atau menutup PR jika tidak selaras dengan arah proyek
|
||||
|
||||
### Setelah PR Anda digabungkan {#after-your-pr-is-merged}
|
||||
|
||||
Kontribusi Anda akan disertakan dalam rilis berikutnya dan dicantumkan dalam changelog.
|
||||
|
||||
## Good first issue {#good-first-issues}
|
||||
|
||||
Mencari sesuatu untuk dikerjakan? Lihat [good first issues](https://github.com/snapotter-hq/snapotter/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) kami untuk tugas yang ramah pemula, atau [help wanted](https://github.com/snapotter-hq/snapotter/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) untuk item yang lebih besar tempat kami menghargai bantuan komunitas.
|
||||
|
||||
## Gaya kode {#code-style}
|
||||
|
||||
- Biome menangani pemformatan dan linting (tanda kutip ganda, titik koma, indentasi 2 spasi)
|
||||
- Hook pra-commit menjalankan `biome check --write` pada file yang di-stage secara otomatis
|
||||
- Jika linter mengeluh, perbaiki kodenya (jangan ubah konfigurasi Biome)
|
||||
- ES module di mana-mana (`import`/`export`)
|
||||
- Conventional commits: `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `chore:`
|
||||
|
||||
Untuk detail arsitektur lengkap, lihat [Panduan Developer](/id/guide/developer).
|
||||
|
||||
## Keamanan {#security}
|
||||
|
||||
**Jangan buka PR atau issue publik untuk kerentanan keamanan.** Laporkan secara privat melalui [GitHub Security Advisories](https://github.com/snapotter-hq/snapotter/security/advisories/new) atau email contact@snapotter.com. Lihat [SECURITY.md](https://github.com/snapotter-hq/snapotter/blob/main/SECURITY.md) untuk detail lengkap.
|
||||
|
||||
## Ada pertanyaan? {#questions}
|
||||
|
||||
- [Dokumentasi](https://docs.snapotter.com/)
|
||||
- [Discord](https://discord.gg/hr3s7HPUsr)
|
||||
- [GitHub Discussions](https://github.com/snapotter-hq/snapotter/discussions)
|
||||
@@ -0,0 +1,185 @@
|
||||
---
|
||||
description: "Skema database PostgreSQL, tabel, migrasi, dan prosedur pencadangan untuk SnapOtter."
|
||||
i18n_source_hash: b37398ae91a3
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 8a963902d4f7
|
||||
---
|
||||
|
||||
# Database {#database}
|
||||
|
||||
SnapOtter menggunakan PostgreSQL 17 dengan [Drizzle ORM](https://orm.drizzle.team/) (pg-core / node-postgres) untuk persistensi data. Skema didefinisikan di `apps/api/src/db/schema.ts`.
|
||||
|
||||
Koneksi dikonfigurasi melalui variabel lingkungan `DATABASE_URL` (default `postgres://snapotter:snapotter@postgres:5432/snapotter`). Di Docker Compose, kontainer Postgres menyimpan datanya di volume bernama `SnapOtter-pgdata`.
|
||||
|
||||
## Tabel {#tables}
|
||||
|
||||
### users {#users}
|
||||
|
||||
Menyimpan akun pengguna. Dibuat otomatis pada saat pertama kali dijalankan dari `DEFAULT_USERNAME` dan `DEFAULT_PASSWORD`.
|
||||
|
||||
| Kolom | Tipe | Catatan |
|
||||
|---|---|---|
|
||||
| `id` | uuid | Primary key |
|
||||
| `username` | varchar | Unik, wajib |
|
||||
| `passwordHash` | varchar | hash scrypt |
|
||||
| `role` | varchar | `admin`, `editor`, atau `user` |
|
||||
| `mustChangePassword` | boolean | Flag reset kata sandi paksa |
|
||||
| `createdAt` | timestamp | Waktu pembuatan |
|
||||
| `updatedAt` | timestamp | Waktu pembaruan terakhir |
|
||||
|
||||
### sessions {#sessions}
|
||||
|
||||
Sesi login aktif. Setiap baris mengaitkan token sesi ke seorang pengguna.
|
||||
|
||||
| Kolom | Tipe | Catatan |
|
||||
|---|---|---|
|
||||
| `id` | varchar | Primary key (token sesi) |
|
||||
| `userId` | uuid | Foreign key ke `users.id` |
|
||||
| `expiresAt` | timestamp | Waktu kedaluwarsa |
|
||||
| `createdAt` | timestamp | Waktu pembuatan |
|
||||
|
||||
### teams {#teams}
|
||||
|
||||
Grup untuk mengorganisasi pengguna. Admin dapat menetapkan pengguna ke tim.
|
||||
|
||||
| Kolom | Tipe | Deskripsi |
|
||||
|--------|------|-------------|
|
||||
| `id` | uuid | Primary key |
|
||||
| `name` | varchar (unik, maks 50 karakter) | Nama tim |
|
||||
| `createdAt` | timestamp | Waktu pembuatan |
|
||||
|
||||
### api_keys {#api-keys}
|
||||
|
||||
API key untuk akses secara programatik. Kunci mentah ditampilkan sekali saat pembuatan; hanya hash yang disimpan.
|
||||
|
||||
| Kolom | Tipe | Catatan |
|
||||
|---|---|---|
|
||||
| `id` | uuid | Primary key |
|
||||
| `userId` | uuid | Foreign key ke `users.id` |
|
||||
| `keyHash` | varchar | hash scrypt dari kunci |
|
||||
| `name` | varchar | Label yang diberikan pengguna |
|
||||
| `createdAt` | timestamp | Waktu pembuatan |
|
||||
| `lastUsedAt` | timestamp | Diperbarui pada setiap permintaan terautentikasi |
|
||||
|
||||
Kunci diberi awalan `si_` diikuti oleh 96 karakter heksadesimal (48 byte acak).
|
||||
|
||||
### pipelines {#pipelines}
|
||||
|
||||
Rangkaian tool tersimpan yang dibuat pengguna di UI.
|
||||
|
||||
| Kolom | Tipe | Catatan |
|
||||
|---|---|---|
|
||||
| `id` | uuid | Primary key |
|
||||
| `name` | varchar | Nama pipeline |
|
||||
| `description` | varchar | Deskripsi opsional |
|
||||
| `steps` | jsonb | Array objek `{ toolId, settings }` |
|
||||
| `createdAt` | timestamp | Waktu pembuatan |
|
||||
|
||||
### user_files {#user-files}
|
||||
|
||||
Pustaka file persisten dengan pelacakan rantai versi. Setiap langkah pemrosesan yang menyimpan hasil membuat baris baru yang tertaut ke induknya melalui `parentId`, membentuk sebuah pohon versi.
|
||||
|
||||
| Kolom | Tipe | Deskripsi |
|
||||
|--------|------|-------------|
|
||||
| `id` | uuid | Primary key |
|
||||
| `userId` | uuid | FK ke users (CASCADE DELETE) |
|
||||
| `originalName` | varchar | Nama file unggahan asli |
|
||||
| `storedName` | varchar | Nama file pada disk |
|
||||
| `mimeType` | varchar | Tipe MIME |
|
||||
| `size` | integer | Ukuran file dalam byte |
|
||||
| `width` | integer | Lebar gambar dalam px |
|
||||
| `height` | integer | Tinggi gambar dalam px |
|
||||
| `version` | integer | Nomor versi (1 = asli) |
|
||||
| `parentId` | uuid atau null | FK ke user_files (versi induk) |
|
||||
| `toolChain` | jsonb | ID tool yang diterapkan secara berurutan untuk menghasilkan versi ini |
|
||||
| `createdAt` | timestamp | Waktu pembuatan |
|
||||
|
||||
### jobs {#jobs}
|
||||
|
||||
Melacak job pemrosesan untuk pelaporan progres dan pembersihan.
|
||||
|
||||
| Kolom | Tipe | Catatan |
|
||||
|---|---|---|
|
||||
| `id` | uuid | Primary key |
|
||||
| `type` | varchar | Identifikasi tool atau pipeline |
|
||||
| `status` | varchar | `queued`, `processing`, `completed`, atau `failed` |
|
||||
| `progress` | real | Fraksi 0.0-1.0 |
|
||||
| `inputFiles` | jsonb | Array path file input |
|
||||
| `outputPath` | varchar | Path ke file hasil |
|
||||
| `settings` | jsonb | Pengaturan tool yang digunakan |
|
||||
| `error` | varchar | Pesan kesalahan jika gagal |
|
||||
| `createdAt` | timestamp | Waktu pembuatan |
|
||||
| `completedAt` | timestamp | Waktu penyelesaian |
|
||||
|
||||
### settings {#settings}
|
||||
|
||||
Penyimpanan key-value untuk pengaturan seluruh server yang dapat diubah admin dari UI.
|
||||
|
||||
| Kolom | Tipe | Catatan |
|
||||
|---|---|---|
|
||||
| `key` | varchar | Primary key |
|
||||
| `value` | varchar | Nilai pengaturan |
|
||||
| `updatedAt` | timestamp | Waktu pembaruan terakhir |
|
||||
|
||||
### roles {#roles}
|
||||
|
||||
Peran kustom dengan izin granular.
|
||||
|
||||
| Kolom | Tipe | Catatan |
|
||||
|---|---|---|
|
||||
| `id` | uuid | Primary key |
|
||||
| `name` | varchar | Nama peran unik |
|
||||
| `description` | varchar | Deskripsi opsional |
|
||||
| `permissions` | jsonb | Array string izin |
|
||||
| `createdAt` | timestamp | Waktu pembuatan |
|
||||
|
||||
### audit_log {#audit-log}
|
||||
|
||||
Log aksi yang relevan dengan keamanan.
|
||||
|
||||
| Kolom | Tipe | Catatan |
|
||||
|---|---|---|
|
||||
| `id` | uuid | Primary key |
|
||||
| `userId` | uuid | FK ke users |
|
||||
| `action` | varchar | Tipe aksi |
|
||||
| `details` | jsonb | Data khusus aksi |
|
||||
| `createdAt` | timestamp | Waktu aksi |
|
||||
|
||||
## Migrasi {#migrations}
|
||||
|
||||
Drizzle menangani migrasi skema. File migrasi berada di `apps/api/drizzle/`. Selama pengembangan:
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
npx drizzle-kit generate # generate a migration from schema changes
|
||||
npx drizzle-kit migrate # apply pending migrations
|
||||
```
|
||||
|
||||
Di produksi, migrasi yang tertunda diterapkan secara otomatis saat startup.
|
||||
|
||||
## Pencadangan dan pemulihan {#backup-and-restore}
|
||||
|
||||
Database relasional berada di volume `SnapOtter-pgdata` kontainer Postgres, bukan di volume `/data` aplikasi.
|
||||
|
||||
**Opsi 1: pg_dump (direkomendasikan)**
|
||||
|
||||
```bash
|
||||
# Dump the database while the stack is running
|
||||
docker exec SnapOtter-postgres pg_dump -U snapotter snapotter > backup.sql
|
||||
|
||||
# Restore into a fresh database
|
||||
cat backup.sql | docker exec -i SnapOtter-postgres psql -U snapotter snapotter
|
||||
```
|
||||
|
||||
**Opsi 2: Snapshot volume**
|
||||
|
||||
```bash
|
||||
# Stop the stack, then snapshot the pgdata volume
|
||||
docker compose down
|
||||
docker run --rm -v SnapOtter-pgdata:/data -v $(pwd)/backup:/backup \
|
||||
alpine tar czf /backup/snapotter-pgdata.tar.gz -C /data .
|
||||
```
|
||||
|
||||
### Migrasi dari 1.x (SQLite) {#migrating-from-1-x-sqlite}
|
||||
|
||||
Memutakhirkan dari SnapOtter 1.x memiliki panduannya sendiri: lihat [Memutakhirkan dari 1.x ke 2.0](./upgrading). Singkatnya, gunakan kembali volume `/data` Anda yang ada dan 2.0 otomatis mendeteksi serta mengimpor `/data/snapotter.db` pada boot pertama (atau atur `SQLITE_MIGRATE_PATH` untuk menunjuk ke sana secara eksplisit). Cadangkan seluruh volume `/data` terlebih dahulu, bukan hanya `snapotter.db`: 1.x menggunakan mode SQLite WAL, sehingga kontainer yang dihentikan sering meninggalkan sebagian besar datanya di `snapotter.db-wal` di samping `snapotter.db` yang hampir kosong.
|
||||
@@ -0,0 +1,571 @@
|
||||
---
|
||||
description: "Deploy SnapOtter ke produksi dengan Docker. Persyaratan perangkat keras, penyiapan GPU, dan konfigurasi reverse proxy untuk Nginx, Traefik, dan Cloudflare."
|
||||
i18n_source_hash: 6b6957060fa6
|
||||
i18n_provenance: machine
|
||||
i18n_output_hash: 45dafa6467df
|
||||
---
|
||||
|
||||
# Deployment {#deployment}
|
||||
|
||||
SnapOtter diterapkan sebagai stack Docker Compose 3 kontainer: image aplikasi SnapOtter, PostgreSQL 17, dan Redis 8. Image aplikasi mendukung **linux/amd64** (dengan NVIDIA CUDA untuk akselerasi AI) dan **linux/arm64** (CPU), sehingga berjalan secara native di server Intel/AMD, Mac Apple Silicon, dan perangkat ARM seperti Raspberry Pi 4/5. Akselerasi iGPU Intel/AMD melalui VA-API, Quick Sync, atau OpenCL saat ini tidak didukung untuk inferensi AI.
|
||||
|
||||
Lihat [Docker Image](./docker-tags) untuk penyiapan GPU, contoh Docker Compose, dan penyematan versi.
|
||||
|
||||
## Quick Start (CPU) {#quick-start-cpu}
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml - Copy this file and run: docker compose up -d
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest # or ghcr.io/snapotter-hq/snapotter:latest
|
||||
container_name: SnapOtter
|
||||
ports:
|
||||
- "1349:1349" # Web UI + API
|
||||
volumes:
|
||||
- SnapOtter-data:/data # AI models, user files (PERSISTENT)
|
||||
- SnapOtter-workspace:/tmp/workspace # Temp processing files (can be tmpfs)
|
||||
environment:
|
||||
# --- Authentication ---
|
||||
- AUTH_ENABLED=true # Set to false to disable login entirely
|
||||
- DEFAULT_USERNAME=admin # First-run admin username
|
||||
- DEFAULT_PASSWORD=admin # First-run admin password (you'll be forced to change it)
|
||||
|
||||
# --- Database + Queue ---
|
||||
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
|
||||
- REDIS_URL=redis://redis:6379
|
||||
|
||||
# --- Limits (set 0 for unlimited) ---
|
||||
# - MAX_UPLOAD_SIZE_MB=100 # Per-file upload limit in MB
|
||||
# - MAX_BATCH_SIZE=100 # Max files per batch request
|
||||
# - RATE_LIMIT_PER_MIN=1000 # API rate limit per IP, default shown (0 = disabled)
|
||||
# - MAX_USERS=0 # Max user accounts
|
||||
|
||||
# --- Networking ---
|
||||
# - TRUST_PROXY=true # Trust X-Forwarded-For headers (set false if not behind a proxy)
|
||||
|
||||
# --- Bind mount permissions ---
|
||||
# - PUID=1000 # Match your host user's UID (run: id -u)
|
||||
# - PGID=1000 # Match your host user's GID (run: id -g)
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 60s
|
||||
retries: 3
|
||||
shm_size: "2gb" # Needed for Python ML shared memory
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
container_name: SnapOtter-postgres
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter # Change this for non-local deployments
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 15s
|
||||
|
||||
redis:
|
||||
image: redis:8-alpine
|
||||
container_name: SnapOtter-redis
|
||||
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- SnapOtter-redisdata:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
SnapOtter-data: # Named volume - Docker manages permissions automatically
|
||||
SnapOtter-workspace:
|
||||
SnapOtter-pgdata:
|
||||
SnapOtter-redisdata:
|
||||
```
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Aplikasi kemudian tersedia di `http://localhost:1349`.
|
||||
|
||||
> **Terkena batas laju Docker Hub?** Ganti `snapotter/snapotter:latest` dengan `ghcr.io/snapotter-hq/snapotter:latest` untuk menarik dari GitHub Container Registry sebagai gantinya. Kedua registry menerima image yang sama pada setiap rilis.
|
||||
|
||||
## Quick Start (NVIDIA CUDA) {#quick-start-nvidia-cuda}
|
||||
|
||||
Untuk akselerasi NVIDIA CUDA pada perkakas AI (penghapusan latar belakang, upscaling, penyempurnaan wajah, OCR):
|
||||
|
||||
```yaml
|
||||
# docker-compose-gpu.yml - Requires: NVIDIA GPU + nvidia-container-toolkit
|
||||
# Install toolkit: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest
|
||||
container_name: SnapOtter
|
||||
ports:
|
||||
- "1349:1349"
|
||||
volumes:
|
||||
- SnapOtter-data:/data
|
||||
- SnapOtter-workspace:/tmp/workspace
|
||||
environment:
|
||||
- AUTH_ENABLED=true
|
||||
- DEFAULT_USERNAME=admin
|
||||
- DEFAULT_PASSWORD=admin
|
||||
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 60s
|
||||
retries: 3
|
||||
shm_size: "2gb" # Required for PyTorch CUDA shared memory
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all # Or set to 1 for a specific GPU
|
||||
capabilities: [gpu]
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
container_name: SnapOtter-postgres
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 15s
|
||||
|
||||
redis:
|
||||
image: redis:8-alpine
|
||||
container_name: SnapOtter-redis
|
||||
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- SnapOtter-redisdata:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
SnapOtter-data:
|
||||
SnapOtter-workspace:
|
||||
SnapOtter-pgdata:
|
||||
SnapOtter-redisdata:
|
||||
```
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose-gpu.yml up -d
|
||||
```
|
||||
|
||||
Periksa deteksi CUDA di log:
|
||||
|
||||
```bash
|
||||
docker logs SnapOtter 2>&1 | head -20
|
||||
# Look for: [gpu] CUDA available via torch
|
||||
```
|
||||
|
||||
## Persyaratan Perangkat Keras {#hardware-requirements}
|
||||
|
||||
Angka-angka ini berasal dari benchmark di berbagai sistem, mulai dari workstation amd64 modern dengan NVIDIA RTX 4070 hingga Raspberry Pi, menjalankan seluruh katalog perkakas pada masing-masing dan menyapu batas sumber daya Docker untuk menemukan batas bawah yang sebenarnya.
|
||||
|
||||
### Referensi Singkat {#quick-reference}
|
||||
|
||||
| Tingkat | Kasus Penggunaan | CPU | RAM | GPU | Penyimpanan |
|
||||
|------|----------|-----|-----|-----|---------|
|
||||
| Minimum | Perkakas gambar, file, dan PDF ringan; satu pengguna; batch kecil | 2 core | 2 GB | Tidak ada | ~7 GB |
|
||||
| Direkomendasikan | Kelima modalitas termasuk video, PDF, dan AI di CPU; batch; beberapa pengguna | 4 core | 4 GB | Tidak ada | ~25 GB |
|
||||
| Penuh | Semuanya dengan kecepatan termasuk AI GPU; batch besar; banyak pengguna | 6-8 core | 8 GB | NVIDIA 8 GB+ VRAM (12 GB nyaman) | ~35 GB |
|
||||
|
||||
**Arsitektur: hanya 64-bit** (`linux/amd64` atau `linux/arm64`). SnapOtter berjalan secara native di server Intel/AMD, Mac Apple Silicon, dan board ARM 64-bit termasuk **Raspberry Pi 4 dan 5** (4-8 GB). SnapOtter **tidak** berjalan di ARM 32-bit (`armv7`/`armhf`), tidak ada image yang dibuat untuknya, maupun di board kelas 512 MB seperti Pi Zero, yang berada di bawah batas bawah memori (lihat di bawah).
|
||||
|
||||
### Minimum (perkakas gambar, file, dan PDF ringan; tanpa AI) {#minimum-image-files-and-light-pdf-tools-no-ai}
|
||||
|
||||
| Sumber Daya | Persyaratan |
|
||||
|---|---|
|
||||
| CPU | 2 core |
|
||||
| RAM | 2 GB |
|
||||
| Disk | ~5.5 GB (image) + volume data |
|
||||
| GPU | Tidak diperlukan |
|
||||
|
||||
Semua 222 perkakas katalog non-AI, yaitu gambar (resize, crop, convert, compress, adjust, watermark), video (trim, mute, remux), audio (convert, normalize, trim), PDF (merge, split, compress, rotate, protect), konversi file, dan preset konversi khusus, berjalan pada perangkat keras sederhana. Sebagian besar operasi selesai jauh di bawah satu detik bahkan pada file besar: gambar 2.7 MB diubah ukurannya dalam ~0.05 d dan dikodekan ulang ke WebP dalam ~2 d.
|
||||
|
||||
Batas bawah memori itu nyata, dari penyapuan batas sumber daya Docker: **512 MB tidak dapat memulai stack** (bahkan satu resize gambar pun dihentikan), **1 GB** menangani operasi satu file tetapi batch multi-file kehabisan memori, dan **2 GB / 2 core** adalah konfigurasi terkecil yang menangani batch dengan nyaman.
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2'
|
||||
memory: 2G
|
||||
```
|
||||
|
||||
**Satu-satunya pengecualian yang berat CPU adalah pengkodean ulang video.** Operasi stream-copy (trim, mute, remux kontainer) instan, tetapi transcoding ke codec berbeda bergantung pada CPU. Klip 1080p / 45 detik yang dikodekan ulang ke VP9 (WebM) memakan waktu kira-kira **~40 d** pada CPU modern yang cepat, ~45 d di Apple Silicon, ~80 d pada 4-core mobile lama, dan **~130 d** pada server 4-core lama. Jika beban kerja Anda banyak video, prioritaskan core CPU dan kecepatan clock, atau naikkan batas `cpus:` kontainer. Compose yang disertakan membatasi aplikasi pada 4 core secara default (8 pada compose GPU).
|
||||
|
||||
### Direkomendasikan (perkakas AI di CPU) {#recommended-ai-tools-on-cpu}
|
||||
|
||||
| Sumber Daya | Persyaratan |
|
||||
|---|---|
|
||||
| CPU | 4 core |
|
||||
| RAM | 4 GB |
|
||||
| Disk | 3 GB (image) + 24 GB (model AI) + workspace |
|
||||
| GPU | Tidak diperlukan (fallback CPU) |
|
||||
|
||||
**Memasang bundle AI-lah yang mendorong RAM ke 4 GB.** Tanpa AI terpasang, aplikasi menganggur di sekitar 360 MB; dengan ketujuh bundle terpasang aplikasi menahan ~2.6 GB resident, karena sidecar AI Python memuat model-modelnya di awal (penghapusan latar belakang, upscaling, OCR, transkripsi, deteksi wajah, restorasi) saat startup. Instalasi non-AI tetap ringan; instalasi AI membutuhkan ≥4 GB.
|
||||
|
||||
Sebagian besar perkakas AI sepenuhnya dapat digunakan di CPU; beberapa benar-benar menginginkan GPU. Diukur pada CPU 4-core modern:
|
||||
|
||||
| Perkakas AI | Waktu CPU | Dapat digunakan di CPU? |
|
||||
|---|---|---|
|
||||
| Deteksi wajah (blur-faces, smart-crop, red-eye), noise-removal | di bawah 1 d | Ya |
|
||||
| OCR, transkripsi, subtitle | 1-3 d | Ya |
|
||||
| Colorize, penyempurnaan wajah | ~10 d | Ya |
|
||||
| Penghapusan / penggantian / blur latar belakang | ~29 d | Ya (Anda akan menunggu) |
|
||||
| AI upscale (RealESRGAN) | ~33 d kecil; menit pada gambar besar | Marginal, GPU sangat direkomendasikan |
|
||||
| Restorasi foto (pipeline penuh) | beberapa menit | Tidak, butuh GPU atau CPU banyak-core yang cepat |
|
||||
|
||||
SnapOtter sengaja tidak memasukkan unduhan model ini ke dalam image Docker. Bundle AI ditarik hanya ketika admin mengaktifkan perkakas terkait, disimpan di volume `/data/ai` yang persisten, dan dibagi oleh setiap perkakas yang bergantung pada stack model yang sama. Ini menjaga image kontainer akhir tetap kecil sekaligus tetap memungkinkan instalasi AI penuh mencapai angka penyimpanan yang lebih besar di bawah.
|
||||
|
||||
Beberapa perkakas bergantung pada lebih dari satu bundle bersama. Misalnya, Passport Photo membutuhkan `background-removal` dan `face-detection`; jika `background-removal` sudah terpasang, mengaktifkan Passport Photo hanya mengunduh bundle `face-detection` yang hilang. Penggunaan ulang yang sama berlaku di semua perkakas AI.
|
||||
|
||||
Ukuran unduhan model AI:
|
||||
|
||||
| Bundle | Ukuran Disk |
|
||||
|---|---|
|
||||
| Penghapusan latar belakang | 4-5 GB |
|
||||
| Upscale + Penyempurnaan wajah + Penghapusan noise | 5-6 GB |
|
||||
| Deteksi wajah | 200-300 MB |
|
||||
| Object eraser + Colorize | 1-2 GB |
|
||||
| OCR | 5-6 GB |
|
||||
| Restorasi foto | 4-5 GB |
|
||||
| **Semua bundle** | **~24 GB** |
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '4'
|
||||
memory: 4G
|
||||
```
|
||||
|
||||
### Penuh (perkakas AI di NVIDIA CUDA) {#full-ai-tools-on-nvidia-cuda}
|
||||
|
||||
| Sumber Daya | Persyaratan |
|
||||
|---|---|
|
||||
| CPU | 6-8 core (persiapan video + konkurensi berjalan di CPU bahkan dengan AI GPU) |
|
||||
| RAM | 8 GB |
|
||||
| GPU | NVIDIA dengan 8+ GB VRAM (12 GB direkomendasikan) |
|
||||
| Disk | ~35 GB total |
|
||||
|
||||
GPU NVIDIA (CUDA) secara dramatis mempercepat model AI yang berat. Diukur pada RTX 4070 vs CPU modern:
|
||||
|
||||
| Perkakas AI | Peningkatan kecepatan dengan GPU | Catatan |
|
||||
|---|---|---|
|
||||
| AI upscale (RealESRGAN 2×) | **~47×** | Kemenangan terbesar, di bawah satu detik vs ~33 d (menit pada gambar besar) |
|
||||
| Penyempurnaan wajah (CodeFormer) | **~12×** | ~0.9 d vs ~11 d |
|
||||
| Transkripsi (Whisper) | ~4.5× | |
|
||||
| Penghapusan / penggantian / blur latar belakang | ~4× | ~7 d di GPU vs ~29 d di CPU |
|
||||
| Colorize | ~1.8× | |
|
||||
| OCR, deteksi wajah, red-eye, noise-removal | ~1× | Sudah cepat di CPU, GPU tidak membantu |
|
||||
| Restorasi foto | tidak ada | Bergantung CPU bahkan di GPU (0% utilisasi GPU); CPU cepat lebih penting daripada GPU di sini |
|
||||
|
||||
Perkakas yang layak menggunakan GPU adalah **upscale, penyempurnaan wajah, transkripsi, dan penghapusan latar belakang**. Deteksi wajah, OCR, dan red-eye bergantung CPU dan sudah cepat, jadi GPU tidak menambah apa pun.
|
||||
|
||||
Penggunaan VRAM puncak mencapai 7.5 GB selama upscale dengan penyempurnaan wajah. GPU NVIDIA 6 GB bekerja untuk sebagian besar perkakas AI secara individual tetapi akan gagal pada upscale. VRAM 8-12 GB menangani semuanya.
|
||||
|
||||
Akselerasi iGPU Intel/AMD melalui VA-API, Quick Sync, atau OpenCL saat ini tidak didukung untuk inferensi AI. Memetakan `/dev/dri` ke dalam kontainer tidak mengaktifkan akselerasi AI GPU; SnapOtter akan menjalankan perkakas AI di CPU kecuali NVIDIA CUDA tersedia.
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '4'
|
||||
memory: 8G
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
```
|
||||
|
||||
### Pengguna Bersamaan {#concurrent-users}
|
||||
|
||||
Permintaan resize gambar paralel terhadap kontainer aplikasi yang dibatasi 4-core secara default:
|
||||
|
||||
| Permintaan Bersamaan | Rata-rata Waktu Respons | Kesalahan |
|
||||
|---|---|---|
|
||||
| 1 | 0.4d | 0 |
|
||||
| 5 | 1.2d | 0 |
|
||||
| 10 | 2.1d | 0 |
|
||||
|
||||
Waktu respons menurun secara sub-linear tanpa kesalahan saat pool worker menjadi jenuh. Menaikkan batas `cpus:` kontainer aplikasi (atau menggunakan host dengan lebih banyak core) mengangkat batas atas. Perhatikan bahwa job berat (transcode video, AI CPU) menahan satu worker selama durasi penuhnya, jadi ukur CPU sesuai jumlah job berat bersamaan yang Anda harapkan, bukan hanya jumlah permintaan.
|
||||
|
||||
### Format Gambar yang Didukung {#supported-image-formats}
|
||||
|
||||
SnapOtter mendukung **55+ format input** dan **14 format output**, termasuk file RAW dari 20+ merek kamera, format profesional (PSD, EPS, OpenEXR, HDR), codec modern (JPEG XL, AVIF, HEIC, QOI), dan format ilmiah/gaming (FITS, DDS).
|
||||
|
||||
Lihat [daftar format lengkap](/id/guide/supported-formats) untuk detail setiap format yang didukung, decoder yang digunakan, dan kontrol kualitas yang tersedia.
|
||||
|
||||
### Batasan yang Diketahui {#known-limitations}
|
||||
|
||||
- **Content-aware resize** crash pada gambar besar (>5 MP) karena batasan pada binary caire. Bekerja baik dengan gambar yang lebih kecil.
|
||||
- **HEIF decode** memakan 13-23 detik. HEIC (varian Apple) jauh lebih cepat pada 0.3-0.9 detik.
|
||||
- **OCR Jepang** gagal di CPU karena bug MKLDNN PaddlePaddle. Bekerja di GPU.
|
||||
- **Upscale** kehabisan waktu di CPU untuk apa pun di luar gambar kecil. GPU diperlukan untuk penggunaan praktis.
|
||||
- **CodeFormer** penyempurnaan wajah jauh lebih lambat daripada GFPGAN (53d vs 2d di GPU). GFPGAN direkomendasikan untuk sebagian besar kasus penggunaan.
|
||||
|
||||
## Volume {#volumes}
|
||||
|
||||
| Mount / Volume | Tujuan | Diperlukan? |
|
||||
|---|---|---|
|
||||
| `/data` (app) | Model AI, venv Python, file pengguna | **Ya**, kehilangan file tanpanya |
|
||||
| `/tmp/workspace` (app) | File pemrosesan sementara (dibersihkan otomatis) | Direkomendasikan |
|
||||
| `SnapOtter-pgdata` (postgres) | Direktori data PostgreSQL (pengguna, pengaturan, pipeline, job) | **Ya**, kehilangan data tanpanya |
|
||||
| `SnapOtter-redisdata` (redis) | File append-only Redis untuk antrean job yang durable | Direkomendasikan |
|
||||
|
||||
### Bind mount vs. named volume {#bind-mounts-vs-named-volumes}
|
||||
|
||||
**Named volume** (direkomendasikan), Docker mengelola izin secara otomatis:
|
||||
```yaml
|
||||
volumes:
|
||||
- SnapOtter-data:/data
|
||||
```
|
||||
|
||||
**Bind mount**, Anda mengelola izin. Atur `PUID`/`PGID` agar cocok dengan pengguna host Anda:
|
||||
```yaml
|
||||
volumes:
|
||||
- ./SnapOtter-data:/data
|
||||
environment:
|
||||
- PUID=1000 # Your host UID (run: id -u)
|
||||
- PGID=1000 # Your host GID (run: id -g)
|
||||
```
|
||||
|
||||
### Izin penyimpanan {#storage-permissions}
|
||||
|
||||
SnapOtter menulis ke dua lokasi saat runtime: `/data` (file pengguna, log, model AI dan venv Python) dan `/tmp/workspace` (scratch pemrosesan sementara). Keduanya harus dapat ditulis oleh pengguna tempat kontainer berjalan. Jika salah satunya tidak, kontainer **gagal cepat saat startup** dengan pesan yang menyebutkan direktori, UID/GID yang berjalan, dan cara memperbaikinya, alih-alih boot "healthy" lalu gagal pada unggahan pertama dengan kesalahan samar.
|
||||
|
||||
Bagaimana izin ditangani bergantung pada cara kontainer diluncurkan:
|
||||
|
||||
**Default (mulai sebagai root, turun ke `snapotter`)**, entrypoint mulai sebagai root, memperbaiki kepemilikan volume yang di-mount, lalu turun ke pengguna `snapotter` yang tidak berhak istimewa melalui `gosu`. Named volume bekerja tanpa konfigurasi. Untuk bind mount, atur `PUID`/`PGID` ke pengguna host Anda (di atas) agar file yang ditulisnya dimiliki oleh Anda.
|
||||
|
||||
**Kubernetes / OpenShift (non-root melalui `runAsUser`)**, diluncurkan langsung sebagai pengguna non-root, kontainer tidak dapat chown volume sendiri, jadi orkestrator harus membuatnya dapat ditulis. Atur `fsGroup`:
|
||||
|
||||
```yaml
|
||||
securityContext:
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
fsGroup: 999 # makes mounted volumes writable by the pod
|
||||
```
|
||||
|
||||
Direktori yang dapat ditulis pada image dimiliki oleh grup GID 0 dan dapat ditulis grup, sehingga pod yang berjalan dengan **UID sembarang** ditambah grup suplementer root (default OpenShift) dapat menulis tanpa `chown`.
|
||||
|
||||
**TrueNAS Scale (dan penyiapan "UID asing" lainnya)**, TrueNAS menjalankan aplikasi sebagai pengguna non-root (sering `568:568`) dan me-mount dataset host yang dimiliki oleh pengguna berbeda, sehingga baik entrypoint maupun `fsGroup` tidak membuatnya dapat ditulis dengan sendirinya. Pilih salah satu:
|
||||
|
||||
- **Jalankan aplikasi sebagai root** (direkomendasikan), biarkan pengguna aplikasi tidak diatur atau atur ke `0`, dan biarkan entrypoint default memperbaiki izin dan turun ke `snapotter`.
|
||||
- **Jalankan sebagai UID `999`**, atur pengguna/grup aplikasi ke `999:999` (pengguna `snapotter` bawaan SnapOtter) agar cocok dengan kepemilikan image.
|
||||
- **`chown` dataset host** ke UID tempat kontainer berjalan, dari shell TrueNAS:
|
||||
|
||||
```bash
|
||||
# Gunakan UID dari kesalahan startup (atau jalankan `id` di dalam kontainer)
|
||||
chown -R 568:568 /mnt/<pool>/<dataset>
|
||||
```
|
||||
|
||||
Kesalahan startup menyebutkan UID persis yang harus digunakan, jadi jalur tercepat adalah memulai aplikasi sekali, membaca pesannya, lalu `chown` (atau menyesuaikan pengguna) sesuai kebutuhan.
|
||||
|
||||
## Variabel Lingkungan {#environment-variables}
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|---|---|---|
|
||||
| `AUTH_ENABLED` | `true` | Aktifkan/nonaktifkan persyaratan login |
|
||||
| `DEFAULT_USERNAME` | `admin` | Username admin awal |
|
||||
| `DEFAULT_PASSWORD` | `admin` | Kata sandi admin awal (dipaksa ganti saat login pertama) |
|
||||
| `MAX_UPLOAD_SIZE_MB` | `100` | Batas unggahan per file |
|
||||
| `MAX_BATCH_SIZE` | `100` | Maksimum file per permintaan batch |
|
||||
| `RATE_LIMIT_PER_MIN` | `1000` | Permintaan API per menit per IP (atur 0 untuk menonaktifkan) |
|
||||
| `MAX_USERS` | `0` (tak terbatas) | Maksimum akun pengguna |
|
||||
| `TRUST_PROXY` | `true` | Percayai header X-Forwarded-For dari reverse proxy |
|
||||
| `PUID` | `999` | Jalankan sebagai UID ini (untuk izin bind mount) |
|
||||
| `PGID` | `999` | Jalankan sebagai GID ini (untuk izin bind mount) |
|
||||
| `LOG_LEVEL` | `info` | Verbositas log: fatal, error, warn, info, debug, trace |
|
||||
| `CONCURRENT_JOBS` | `0` (otomatis) | Maksimum job pemrosesan AI paralel |
|
||||
| `SESSION_DURATION_HOURS` | `168` | Masa berlaku sesi login (7 hari) |
|
||||
| `CORS_ORIGIN` | (kosong) | Origin yang diizinkan dipisahkan koma, atau kosong untuk same-origin |
|
||||
|
||||
## Health Check {#health-check}
|
||||
|
||||
Kontainer menyertakan health check bawaan:
|
||||
|
||||
```bash
|
||||
# Check container health status
|
||||
docker inspect --format='{{.State.Health.Status}}' SnapOtter
|
||||
|
||||
# Manual health check
|
||||
curl http://localhost:1349/api/v1/health
|
||||
# {"status":"healthy","version":"x.y.z"}
|
||||
```
|
||||
|
||||
## Reverse Proxy {#reverse-proxy}
|
||||
|
||||
SnapOtter mengatur `TRUST_PROXY=true` secara default sehingga pembatasan laju dan logging menggunakan IP klien sebenarnya dari header `X-Forwarded-For`.
|
||||
|
||||
### Nginx {#nginx}
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name images.example.com;
|
||||
|
||||
# Match MAX_UPLOAD_SIZE_MB (0 = nginx default 1M, so set high for unlimited)
|
||||
client_max_body_size 500M;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:1349;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# SSE support (batch progress, feature install progress)
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Nginx Proxy Manager {#nginx-proxy-manager}
|
||||
|
||||
1. Tambahkan Proxy Host baru
|
||||
2. Atur Domain Name ke domain Anda
|
||||
3. Atur Scheme ke `http`, Forward Hostname ke `SnapOtter` (atau IP kontainer Anda), Forward Port ke `1349`
|
||||
4. Aktifkan dukungan WebSocket
|
||||
5. Di bawah Advanced, tambahkan: `client_max_body_size 500M;` dan `proxy_buffering off;`
|
||||
|
||||
### Traefik {#traefik}
|
||||
|
||||
```yaml
|
||||
# Add these labels to the SnapOtter service in docker-compose.yml
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.snapotter.rule=Host(`images.example.com`)"
|
||||
- "traefik.http.routers.snapotter.entrypoints=websecure"
|
||||
- "traefik.http.routers.snapotter.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.snapotter.loadbalancer.server.port=1349"
|
||||
# Increase upload limit (default 2MB is too low)
|
||||
- "traefik.http.middlewares.snapotter-body.buffering.maxRequestBodyBytes=524288000"
|
||||
- "traefik.http.routers.snapotter.middlewares=snapotter-body"
|
||||
```
|
||||
|
||||
### Caddy {#caddy}
|
||||
|
||||
```txt
|
||||
images.example.com {
|
||||
reverse_proxy localhost:1349 {
|
||||
flush_interval -1
|
||||
transport http {
|
||||
read_timeout 300s
|
||||
write_timeout 300s
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`flush_interval -1` menonaktifkan buffering respons, yang diperlukan untuk event progres SSE (pemrosesan batch, perkakas AI, instalasi fitur). Timeout yang diperpanjang memungkinkan unggahan file besar selesai tanpa Caddy menutup koneksi terlalu dini.
|
||||
|
||||
### Cloudflare Tunnels {#cloudflare-tunnels}
|
||||
|
||||
```bash
|
||||
cloudflared tunnel --url http://localhost:1349
|
||||
```
|
||||
|
||||
Catatan: Cloudflare memiliki batas unggahan 100 MB pada paket gratis. Atur `MAX_UPLOAD_SIZE_MB=100` agar cocok.
|
||||
|
||||
## CI/CD {#ci-cd}
|
||||
|
||||
Repositori GitHub memiliki tiga workflow:
|
||||
|
||||
- **ci.yml**, Berjalan otomatis pada setiap push dan PR. Melakukan lint, typecheck, test, build, dan memvalidasi image Docker (tanpa push).
|
||||
- **release.yml**, Dipicu secara manual melalui `workflow_dispatch`. Menjalankan semantic-release untuk membuat tag versi dan rilis GitHub, lalu membangun image Docker multi-arch (amd64 + arm64) dan mendorong ke Docker Hub (`snapotter/snapotter`) dan GitHub Container Registry (`ghcr.io/snapotter-hq/snapotter`).
|
||||
- **deploy-docs.yml**, Membangun situs dokumentasi ini dan menerapkannya ke Cloudflare Pages saat push ke `main`.
|
||||
|
||||
Untuk membuat rilis, buka **Actions > Release > Run workflow** di UI GitHub, atau jalankan:
|
||||
|
||||
```bash
|
||||
gh workflow run release.yml
|
||||
```
|
||||
|
||||
Semantic-release menentukan versi dari riwayat commit. Tag Docker `latest` selalu menunjuk ke rilis terbaru.
|
||||
|
||||
## Analitik {#analytics}
|
||||
|
||||
SnapOtter menyertakan analitik produk anonim (pola penggunaan perkakas, laporan kesalahan) untuk membantu menangkap bug dan meningkatkan fitur. Ini aktif secara default. File Anda, nama file, dan data pribadi tidak pernah menjadi bagian dari ini. SnapOtter bekerja normal dengan analitik dinonaktifkan.
|
||||
|
||||
### Menonaktifkan analitik {#disabling-analytics}
|
||||
|
||||
Opt-out runtime adalah toggle admin satu klik. Buka Settings > System > Privacy dan matikan Anonymous Product Analytics. Analitik berhenti segera untuk seluruh instance, tanpa rebuild diperlukan.
|
||||
|
||||
Untuk image yang tidak akan pernah memancarkan analitik, atur hard-off build-time dengan mengkloning repositori dan membangun ulang:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/snapotter-hq/SnapOtter.git
|
||||
cd SnapOtter
|
||||
docker compose -f docker/docker-compose.yml build --build-arg SNAPOTTER_ANALYTICS=off
|
||||
docker compose -f docker/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
Atau tambahkan build arg ke `docker-compose.yml` Anda yang sudah ada:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
snapotter:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile
|
||||
args:
|
||||
SNAPOTTER_ANALYTICS: "off"
|
||||
```
|
||||
@@ -0,0 +1,234 @@
|
||||
---
|
||||
description: "Penyiapan pengembangan lokal, perintah, konvensi kode, dan cara menambahkan tool baru ke SnapOtter."
|
||||
i18n_source_hash: cb03724d2829
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 3702cfaac3e7
|
||||
---
|
||||
|
||||
# Panduan developer {#developer-guide}
|
||||
|
||||
Cara menyiapkan lingkungan pengembangan lokal dan berkontribusi kode ke SnapOtter.
|
||||
|
||||
## Prasyarat {#prerequisites}
|
||||
|
||||
- [Node.js](https://nodejs.org/) 22+
|
||||
- [pnpm](https://pnpm.io/) 9+ (`corepack enable && corepack prepare pnpm@latest --activate`)
|
||||
- [Docker](https://www.docker.com/) (diperlukan untuk Postgres + Redis lokal, build kontainer, dan fitur AI)
|
||||
- Git
|
||||
|
||||
Python 3.10+ hanya diperlukan jika Anda mengerjakan sidecar AI/ML (penghapusan latar belakang, upscaling, OCR).
|
||||
|
||||
## Penyiapan {#setup}
|
||||
|
||||
```bash
|
||||
git clone https://github.com/snapotter-hq/snapotter.git
|
||||
cd snapotter
|
||||
docker compose -f docker-compose.dev.yml up -d # start Postgres + Redis
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Ini memulai dua server dev:
|
||||
|
||||
| Layanan | URL | Catatan |
|
||||
|----------|--------------------------|------------------------------------|
|
||||
| Frontend | http://localhost:1349 | Server dev Vite, mem-proxy /api |
|
||||
| Backend | http://localhost:13490 | Fastify API (diakses melalui proxy) |
|
||||
|
||||
Buka http://localhost:1349 di browser Anda. Login dengan `admin` / `admin`. Anda akan diminta untuk mengubah kata sandi saat login pertama.
|
||||
|
||||
## Struktur proyek {#project-structure}
|
||||
|
||||
```
|
||||
apps/
|
||||
api/ Fastify backend
|
||||
web/ Vite + React frontend
|
||||
docs/ VitePress documentation (this site)
|
||||
packages/
|
||||
shared/ Constants, types, i18n strings
|
||||
image-engine/ Sharp-based image operations
|
||||
media-engine/ FFmpeg spawn + progress parsing
|
||||
doc-engine/ qpdf, LibreOffice, ghostscript wrappers
|
||||
ai/ Python sidecar bridge for ML models
|
||||
tests/
|
||||
unit/ Vitest unit tests
|
||||
integration/ Vitest integration tests (full API)
|
||||
e2e/ Playwright end-to-end specs
|
||||
fixtures/ Small test images
|
||||
```
|
||||
|
||||
## Perintah {#commands}
|
||||
|
||||
```bash
|
||||
pnpm dev # start frontend + backend
|
||||
pnpm build # build all workspaces
|
||||
pnpm typecheck # TypeScript check across monorepo
|
||||
pnpm lint # Biome lint + format check
|
||||
pnpm lint:fix # auto-fix lint + format
|
||||
pnpm test # unit + integration tests
|
||||
pnpm test:unit # unit tests only
|
||||
pnpm test:integration # integration tests only
|
||||
pnpm test:e2e # Playwright e2e tests
|
||||
pnpm test:coverage # tests with coverage report
|
||||
```
|
||||
|
||||
## Konvensi kode {#code-conventions}
|
||||
|
||||
- Tanda kutip ganda, titik koma, indentasi 2 spasi (ditegakkan oleh Biome)
|
||||
- ES module di semua workspace
|
||||
- [Conventional commits](https://www.conventionalcommits.org/) untuk semantic-release
|
||||
- Zod untuk semua validasi input API
|
||||
- Tidak ada modifikasi pada file konfigurasi Biome, TypeScript, atau editor. Perbaiki kodenya, bukan linter-nya.
|
||||
|
||||
## Database {#database}
|
||||
|
||||
PostgreSQL 17 melalui Drizzle ORM (pg-core). Dev lokal memerlukan Postgres dan Redis yang berjalan - mulai keduanya dengan:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
```
|
||||
|
||||
Ini memberi Anda Postgres pada port 5432 dan Redis pada port 6379. Kemudian buat dan terapkan migrasi:
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
npx drizzle-kit generate # generate a migration from schema changes
|
||||
npx drizzle-kit migrate # apply pending migrations
|
||||
```
|
||||
|
||||
Skema didefinisikan di `apps/api/src/db/schema.ts`. Tabel: users, sessions, settings, jobs, apiKeys, pipelines, teams, userFiles, roles, auditLog.
|
||||
|
||||
## Menambahkan tool baru {#adding-a-new-tool}
|
||||
|
||||
Setiap tool mengikuti pola yang sama. Berikut contoh minimal.
|
||||
|
||||
### 1. Route backend {#_1-backend-route}
|
||||
|
||||
Buat `apps/api/src/routes/tools/my-tool.ts`:
|
||||
|
||||
```ts
|
||||
import { z } from "zod";
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { createToolRoute } from "../tool-factory.js";
|
||||
|
||||
const settingsSchema = z.object({
|
||||
intensity: z.number().min(0).max(100).default(50),
|
||||
});
|
||||
|
||||
export function registerMyTool(app: FastifyInstance) {
|
||||
createToolRoute(app, {
|
||||
toolId: "my-tool",
|
||||
settingsSchema,
|
||||
async process(inputBuffer, settings, filename) {
|
||||
// Use sharp or other libraries to process the image
|
||||
const sharp = (await import("sharp")).default;
|
||||
const result = await sharp(inputBuffer)
|
||||
// ... your processing logic
|
||||
.toBuffer();
|
||||
|
||||
return {
|
||||
buffer: result,
|
||||
filename: filename.replace(/\.[^.]+$/, ".png"),
|
||||
contentType: "image/png",
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Kemudian daftarkan di `apps/api/src/routes/tools/index.ts`.
|
||||
|
||||
### 2. Komponen pengaturan frontend {#_2-frontend-settings-component}
|
||||
|
||||
Buat `apps/web/src/components/tools/my-tool-settings.tsx`:
|
||||
|
||||
```tsx
|
||||
import { useState } from "react";
|
||||
import { useToolProcessor } from "@/hooks/use-tool-processor";
|
||||
import { useFileStore } from "@/stores/file-store";
|
||||
|
||||
export function MyToolSettings() {
|
||||
const { files } = useFileStore();
|
||||
const { processFiles, processing, error, downloadUrl } =
|
||||
useToolProcessor("my-tool");
|
||||
|
||||
const [intensity, setIntensity] = useState(50);
|
||||
|
||||
const handleProcess = () => {
|
||||
processFiles(files, { intensity });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* your controls here */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleProcess}
|
||||
disabled={files.length === 0 || processing}
|
||||
data-testid="my-tool-submit"
|
||||
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50"
|
||||
>
|
||||
Process
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Kemudian daftarkan di registry tool frontend di `apps/web/src/lib/tool-registry.tsx`:
|
||||
|
||||
```tsx
|
||||
// Add the lazy import
|
||||
const MyToolSettings = lazy(() =>
|
||||
import("@/components/tools/my-tool-settings").then((m) => ({
|
||||
default: m.MyToolSettings,
|
||||
})),
|
||||
);
|
||||
|
||||
// Add to the toolRegistry Map
|
||||
["my-tool", { displayMode: "before-after", Settings: MyToolSettings }],
|
||||
```
|
||||
|
||||
Mode tampilan: `"side-by-side"`, `"before-after"`, `"live-preview"`, `"no-comparison"`, `"interactive-crop"`, `"interactive-eraser"`, `"no-dropzone"`.
|
||||
|
||||
### 3. Entri i18n {#_3-i18n-entry}
|
||||
|
||||
Tambahkan ke `packages/shared/src/i18n/en.ts`:
|
||||
|
||||
```ts
|
||||
"my-tool": {
|
||||
name: "My Tool",
|
||||
description: "Short description of what this tool does",
|
||||
},
|
||||
```
|
||||
|
||||
### 4. Tes {#_4-tests}
|
||||
|
||||
Tambahkan atribut `data-testid` ke tombol aksi Anda (seperti ditunjukkan di atas) agar tes e2e dapat menargetkannya secara andal.
|
||||
|
||||
## Build Docker {#docker-builds}
|
||||
|
||||
Bangun image produksi lengkap secara lokal:
|
||||
|
||||
```bash
|
||||
docker build -f docker/Dockerfile -t snapotter:latest .
|
||||
```
|
||||
|
||||
Gunakan cache mount BuildKit untuk build ulang yang lebih cepat:
|
||||
|
||||
```bash
|
||||
DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile -t snapotter:latest .
|
||||
```
|
||||
|
||||
## Variabel lingkungan {#environment-variables}
|
||||
|
||||
Lihat [Panduan Konfigurasi](/id/guide/configuration) untuk daftar lengkap. Yang penting untuk pengembangan:
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|-----------------------------|-----------|------------------------------------------------|
|
||||
| `AUTH_ENABLED` | `true` | Aktifkan/nonaktifkan autentikasi |
|
||||
| `DEFAULT_USERNAME` | `admin` | Nama pengguna admin default |
|
||||
| `DEFAULT_PASSWORD` | `admin` | Kata sandi admin default |
|
||||
| `SKIP_MUST_CHANGE_PASSWORD` | `false` | Lewati perubahan kata sandi paksa (hanya CI/dev) |
|
||||
| `RATE_LIMIT_PER_MIN` | `1000` | Batas laju API per menit (0 = dinonaktifkan) |
|
||||
| `MAX_UPLOAD_SIZE_MB` | `100` | Ukuran unggah maksimum dalam MB (0 = tak terbatas) |
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
description: "Tag image Docker SnapOtter, benchmark GPU, penyematan versi, dan dukungan multi-platform untuk AMD64 dan ARM64."
|
||||
i18n_source_hash: 148b3608e11a
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 1285488cc707
|
||||
---
|
||||
|
||||
# Image Docker {#docker-image}
|
||||
|
||||
SnapOtter dikirim sebagai satu image Docker. Jalankan sendiri dan ia akan memulai PostgreSQL 17 tertanam serta Redis pada antarmuka loopback (mode tertanam); untuk produksi, jalankan bersama container PostgreSQL 17 dan Redis 8 terpisah dengan Compose. Image aplikasi ini bekerja di semua platform.
|
||||
|
||||
## Mulai cepat {#quick-start}
|
||||
|
||||
```bash
|
||||
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
|
||||
```
|
||||
|
||||
Tanpa `DATABASE_URL` yang disetel, ini berjalan dalam mode tertanam: PostgreSQL dan Redis dimulai di dalam container pada loopback, dengan semua data di bawah volume `SnapOtter-data`. Setel `DATABASE_URL` dan `REDIS_URL` (seperti yang dilakukan stack [Compose](#docker-compose)) untuk menggunakan layanan eksternal sebagai gantinya. Lihat [Konfigurasi](/id/guide/configuration#embedded-mode).
|
||||
|
||||
## Akselerasi NVIDIA CUDA {#nvidia-cuda-acceleration}
|
||||
|
||||
Image menyertakan dukungan NVIDIA CUDA pada amd64. Jika Anda memiliki GPU NVIDIA dengan [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) terpasang, tambahkan `--gpus all`:
|
||||
|
||||
```bash
|
||||
docker run -d --name SnapOtter --gpus all -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
|
||||
```
|
||||
|
||||
Image mendeteksi CUDA secara otomatis saat runtime. Tanpa `--gpus all`, atau saat CUDA tidak tersedia, tool AI berjalan di CPU. Image yang sama untuk keduanya.
|
||||
|
||||
Akselerasi iGPU Intel/AMD melalui VA-API, Quick Sync, atau OpenCL saat ini tidak didukung untuk inferensi AI SnapOtter. Memetakan `/dev/dri` ke dalam container dapat mengekspos perangkat render, tetapi runtime AI akan tetap menggunakan CPU kecuali CUDA tersedia.
|
||||
|
||||
### Benchmark {#benchmarks}
|
||||
|
||||
Diuji pada NVIDIA RTX 4070 (VRAM 12 GB) dengan potret JPEG 572x1024.
|
||||
|
||||
#### Performa hangat {#warm-performance}
|
||||
|
||||
| Tool | CPU | GPU | Percepatan |
|
||||
|------|-----|-----|---------|
|
||||
| Penghapusan latar (u2net) | 2.415ms | 879ms | 2,7x |
|
||||
| Penghapusan latar (isnet) | 2.457ms | 1.137ms | 2,2x |
|
||||
| Upscale 2x | 350ms | 309ms | 1,1x |
|
||||
| Upscale 4x | 910ms | 310ms | 2,9x |
|
||||
| OCR (PaddleOCR) | 137ms | 94ms | 1,5x |
|
||||
| Blur wajah | 139ms | 122ms | 1,1x |
|
||||
|
||||
#### Cold start (permintaan pertama setelah container mulai) {#cold-start-first-request-after-container-start}
|
||||
|
||||
| Tool | CPU | GPU | Percepatan |
|
||||
|------|-----|-----|---------|
|
||||
| Penghapusan latar | 22.286ms | 4.792ms | 4,7x |
|
||||
| Upscale 2x | 3.957ms | 2.318ms | 1,7x |
|
||||
| OCR (PaddleOCR) | 1.469ms | 1.090ms | 1,3x |
|
||||
|
||||
### Pemeriksaan kesehatan CUDA {#cuda-health-check}
|
||||
|
||||
Setelah permintaan AI pertama, endpoint kesehatan admin melaporkan status GPU CUDA:
|
||||
|
||||
```
|
||||
GET /api/v1/admin/health
|
||||
{"ai": {"gpu": true}}
|
||||
```
|
||||
|
||||
## Docker Compose {#docker-compose}
|
||||
|
||||
Stack Compose lengkap mencakup aplikasi, PostgreSQL 17, dan Redis 8. Lihat [Penerapan](/id/guide/deployment) untuk `docker-compose.yml` lengkap. Contoh minimal:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest
|
||||
ports:
|
||||
- "1349:1349"
|
||||
volumes:
|
||||
- SnapOtter-data:/data
|
||||
- SnapOtter-workspace:/tmp/workspace
|
||||
environment:
|
||||
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
redis:
|
||||
image: redis:8-alpine
|
||||
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- SnapOtter-redisdata:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
volumes:
|
||||
SnapOtter-data:
|
||||
SnapOtter-workspace:
|
||||
SnapOtter-pgdata:
|
||||
SnapOtter-redisdata:
|
||||
```
|
||||
|
||||
Untuk akselerasi NVIDIA CUDA via Docker Compose, tambahkan bagian deploy ke layanan SnapOtter:
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
```
|
||||
|
||||
## Penyematan versi {#version-pinning}
|
||||
|
||||
| Tag | Deskripsi |
|
||||
|-----|------------|
|
||||
| `latest` | Rilis terbaru |
|
||||
| `1.11.0` | Versi persis |
|
||||
| `1.11` | Patch terbaru di 1.11.x |
|
||||
| `1` | Minor terbaru di 1.x |
|
||||
|
||||
## Platform {#platforms}
|
||||
|
||||
| Arsitektur | Dukungan GPU | Catatan |
|
||||
|---|---|---|
|
||||
| linux/amd64 | NVIDIA CUDA | Akselerasi CUDA penuh untuk tool AI |
|
||||
| linux/arm64 | Hanya CPU | Raspberry Pi 4/5, Apple Silicon via Docker Desktop |
|
||||
|
||||
## Migrasi dari tag sebelumnya {#migration-from-previous-tags}
|
||||
|
||||
Jika Anda sebelumnya menggunakan tag `:cuda`, beralihlah ke `:latest` dan pertahankan `--gpus all`. Dukungan GPU sama, image terpadu.
|
||||
|
||||
Data dan pengaturan Anda dipertahankan di dalam volume.
|
||||
@@ -0,0 +1,176 @@
|
||||
---
|
||||
description: "Pasang SnapOtter dengan Docker dalam satu perintah. Termasuk penyiapan Docker Compose, membangun dari sumber, dan gambaran lengkap fitur."
|
||||
i18n_source_hash: 4536d4558b8e
|
||||
i18n_provenance: machine
|
||||
i18n_output_hash: 6df615cedb03
|
||||
---
|
||||
|
||||
# Memulai {#getting-started}
|
||||
|
||||
::: tip Coba sebelum memasang
|
||||
Jelajahi UI lengkap di [demo.snapotter.com](https://demo.snapotter.com), tanpa pendaftaran atau instalasi diperlukan.
|
||||
:::
|
||||
|
||||
## Quick Start {#quick-start}
|
||||
|
||||
```bash
|
||||
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
|
||||
```
|
||||
|
||||
Kontainer tunggal ini menjalankan semua yang dibutuhkannya: tanpa `DATABASE_URL` yang diatur, ia memulai PostgreSQL dan Redis sendiri pada antarmuka loopback (mode embedded) dan menyimpan semua data di volume `SnapOtter-data`. Ini adalah cara tercepat untuk mencoba SnapOtter atau self-host di homelab. Untuk produksi, jalankan stack [Docker Compose](#docker-compose) di bawah, yang menjaga PostgreSQL dan Redis di kontainer mereka sendiri. Mode embedded berjalan sebagai root (default) dan mati secara otomatis segera setelah Anda mengatur `DATABASE_URL`.
|
||||
|
||||
Anda akan diminta mengubah kata sandi Anda saat login pertama.
|
||||
|
||||
::: tip Analitik Produk Anonim
|
||||
SnapOtter menyertakan analitik produk anonim secara default. Untuk mematikannya, buka **Settings → System → Privacy** dan matikan **Anonymous Product Analytics**. Analitik berhenti segera untuk seluruh instance.
|
||||
|
||||
Anda juga dapat mengatur variabel lingkungan `SNAPOTTER_TELEMETRY=0` (`false` dan `off` juga berfungsi) untuk menonaktifkan semua telemetri untuk instance tanpa rebuild.
|
||||
|
||||
Pemantauan kesalahan didukung oleh [Sentry](https://sentry.io), yang mensponsori SnapOtter melalui program open-source-nya.
|
||||
|
||||
Untuk detail tentang apa yang dikumpulkan, lihat [Apa yang dikumpulkan SnapOtter](/id/guide/telemetry).
|
||||
:::
|
||||
|
||||
::: tip Akselerasi NVIDIA CUDA
|
||||
Tambahkan `--gpus all` untuk penghapusan latar belakang, upscaling, OCR, penyempurnaan wajah, dan restorasi yang diakselerasi NVIDIA CUDA:
|
||||
|
||||
```bash
|
||||
docker run -d --name SnapOtter -p 1349:1349 --gpus all -v SnapOtter-data:/data snapotter/snapotter:latest
|
||||
```
|
||||
|
||||
Membutuhkan [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html). Otomatis kembali ke CPU ketika CUDA tidak tersedia. Akselerasi iGPU Intel/AMD melalui VA-API, Quick Sync, atau OpenCL saat ini tidak didukung untuk inferensi AI. Lihat [Docker Tags](/id/guide/docker-tags) untuk benchmark.
|
||||
:::
|
||||
|
||||
::: details Juga di GHCR
|
||||
```bash
|
||||
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data ghcr.io/snapotter-hq/snapotter:latest
|
||||
```
|
||||
|
||||
Kedua registry mempublikasikan image yang sama pada setiap rilis.
|
||||
:::
|
||||
|
||||
## Docker Compose {#docker-compose}
|
||||
|
||||
```yaml
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest # or ghcr.io/snapotter-hq/snapotter:latest
|
||||
ports:
|
||||
- "1349:1349"
|
||||
volumes:
|
||||
- SnapOtter-data:/data
|
||||
environment:
|
||||
- AUTH_ENABLED=true
|
||||
- DEFAULT_USERNAME=admin
|
||||
- DEFAULT_PASSWORD=admin
|
||||
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
redis:
|
||||
image: redis:8-alpine
|
||||
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- SnapOtter-redisdata:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
volumes:
|
||||
SnapOtter-data:
|
||||
SnapOtter-pgdata:
|
||||
SnapOtter-redisdata:
|
||||
```
|
||||
|
||||
Lihat [Configuration](/id/guide/configuration) untuk semua variabel lingkungan.
|
||||
|
||||
## Membangun dari Sumber {#build-from-source}
|
||||
|
||||
**Prasyarat:** Node.js 22+, pnpm 9+, Docker (untuk Postgres + Redis), Python 3.10+ (untuk fitur AI), Git.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/snapotter-hq/SnapOtter.git
|
||||
cd SnapOtter
|
||||
docker compose -f docker-compose.dev.yml up -d # start Postgres + Redis
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
- Frontend: [http://localhost:1349](http://localhost:1349)
|
||||
- Backend: [http://localhost:13490](http://localhost:13490)
|
||||
|
||||
## Apa yang Bisa Anda Lakukan {#what-you-can-do}
|
||||
|
||||
### Pemrosesan File (200+ Perkakas) {#file-processing-200-tools}
|
||||
|
||||
| Modalitas | Jumlah | Contoh Perkakas |
|
||||
|----------|-------|---------------|
|
||||
| **Gambar** | 105 | Resize, Crop, Compress, Convert, Remove Background, Upscale, OCR, Watermark, Collage, Colorize, GIF Tools, preset format |
|
||||
| **Video** | 57 | Trim, Crop, Compress, Convert, Merge, Extract Audio, Auto Subtitles, Video to GIF, Resize, Stabilize, preset format |
|
||||
| **Audio** | 27 | Trim, Merge, Convert, Normalize, Noise Reduction, Transcribe, Pitch Shift, Fade, Ringtone Maker, preset format |
|
||||
| **PDF / Dokumen** | 42 | Merge, Split, Compress, OCR, Watermark, Redact, Word to PDF, Excel to PDF, Rotate, Protect, Repair |
|
||||
| **File** | 10 | CSV to JSON, JSON to XML, Merge CSVs, Split CSV, Create ZIP, Extract ZIP, Chart Maker, YAML/JSON |
|
||||
|
||||
### Pipeline {#pipelines}
|
||||
|
||||
Rangkai perkakas menjadi alur kerja multi-langkah dan terapkan ke satu gambar atau seluruh batch:
|
||||
|
||||
1. Buka **Pipelines** di sidebar.
|
||||
2. Tambahkan langkah (perkakas apa pun, pengaturan apa pun).
|
||||
3. Jalankan pada satu file, atau seluruh batch sekaligus.
|
||||
4. Simpan pipeline untuk digunakan kembali nanti.
|
||||
|
||||
Pipeline mengizinkan 20 langkah secara default. Atur `MAX_PIPELINE_STEPS=0` untuk membuat batas tak terbatas.
|
||||
|
||||
### Pustaka File {#file-library}
|
||||
|
||||
Setiap file yang Anda proses dapat disimpan ke pustaka **Files** Anda. SnapOtter melacak riwayat versi lengkap sehingga Anda dapat menelusuri setiap langkah pemrosesan dari unggahan asli hingga output akhir.
|
||||
|
||||
Penyimpanan bersifat eksplisit: hasil yang Anda simpan ke pustaka disimpan hingga Anda menghapusnya, sedangkan hasil yang Anda proses dan biarkan tidak disimpan dihapus otomatis setelah 72 jam (dapat dikonfigurasi melalui `FILE_MAX_AGE_HOURS`).
|
||||
|
||||
### REST API & API Key {#rest-api-api-keys}
|
||||
|
||||
Setiap perkakas dapat diakses melalui HTTP:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:1349/api/v1/tools/image/resize \
|
||||
-H "Authorization: Bearer si_<your-api-key>" \
|
||||
-F "file=@photo.jpg" \
|
||||
-F 'settings={"width":800,"height":600,"fit":"cover"}'
|
||||
```
|
||||
|
||||
Buat API key di bawah **Settings → API Keys**. Lihat [referensi REST API](/id/api/rest) untuk semua endpoint, atau kunjungi [http://localhost:1349/api/docs](http://localhost:1349/api/docs) untuk referensi interaktif.
|
||||
|
||||
### Multi-Pengguna & Tim {#multi-user-teams}
|
||||
|
||||
Aktifkan beberapa pengguna dengan kontrol akses berbasis peran:
|
||||
|
||||
- **Admin**: akses penuh, kelola pengguna, tim, pengaturan, semua file/pipeline/API key
|
||||
- **Pengguna**: gunakan perkakas, kelola file/pipeline/API key sendiri
|
||||
|
||||
Buat tim di bawah **Settings → Teams** untuk mengelompokkan pengguna.
|
||||
|
||||
Atur `AUTH_ENABLED=true` (atau `false` untuk penggunaan tunggal/pribadi tanpa login).
|
||||
@@ -0,0 +1,170 @@
|
||||
---
|
||||
description: "Siapkan Single Sign-On dengan OpenID Connect. Panduan langkah demi langkah untuk Keycloak, Authentik, Google, dan penyedia OIDC lainnya."
|
||||
i18n_source_hash: 4296343b3cc5
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 5e6d2a00ca73
|
||||
---
|
||||
|
||||
# OIDC / Single Sign-On {#oidc-single-sign-on}
|
||||
|
||||
SnapOtter mendukung OpenID Connect (OIDC) untuk single sign-on. Pengguna dapat login dengan penyedia identitas eksternal seperti Keycloak, Authentik, atau Google alih-alih (atau bersama) autentikasi username/kata sandi lokal.
|
||||
|
||||
::: tip Lihat juga
|
||||
[SAML SSO](/id/guide/saml) | [Provisioning SCIM](/id/guide/scim) | [Pengguna, Peran & Izin](/id/guide/users-roles)
|
||||
:::
|
||||
|
||||
## Mulai cepat {#quick-start}
|
||||
|
||||
Tambahkan variabel lingkungan ini ke `docker-compose.yml` Anda:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest
|
||||
environment:
|
||||
EXTERNAL_URL: "https://photos.example.com"
|
||||
OIDC_ENABLED: "true"
|
||||
OIDC_ISSUER_URL: "https://auth.example.com/realms/myrealm"
|
||||
OIDC_CLIENT_ID: "snapotter"
|
||||
OIDC_CLIENT_SECRET: "your-secret-here"
|
||||
```
|
||||
|
||||
Redirect URI untuk penyedia Anda selalu:
|
||||
|
||||
```
|
||||
${EXTERNAL_URL}/api/auth/oidc/callback
|
||||
```
|
||||
|
||||
Misalnya, jika `EXTERNAL_URL` adalah `https://photos.example.com`, konfigurasikan redirect URI penyedia Anda sebagai `https://photos.example.com/api/auth/oidc/callback`.
|
||||
|
||||
## Referensi konfigurasi {#configuration-reference}
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|---|---|---|
|
||||
| `OIDC_ENABLED` | `false` | Aktifkan login OIDC. Tombol "Sign in with SSO" muncul di halaman login. |
|
||||
| `OIDC_ISSUER_URL` | | URL issuer penyedia. Harus mendukung OIDC Discovery (`/.well-known/openid-configuration`). |
|
||||
| `OIDC_CLIENT_ID` | | Client ID OAuth yang terdaftar dengan penyedia Anda. |
|
||||
| `OIDC_CLIENT_SECRET` | | Client secret OAuth. |
|
||||
| `OIDC_SCOPES` | `openid profile email` | Daftar scope yang diminta, dipisahkan spasi. |
|
||||
| `OIDC_AUTO_CREATE_USERS` | `true` | Buat akun pengguna lokal secara otomatis saat login OIDC pertama. |
|
||||
| `OIDC_DEFAULT_ROLE` | `user` | Peran yang diberikan ke pengguna OIDC yang dibuat otomatis. Salah satu dari `admin`, `editor`, atau `user`. |
|
||||
| `OIDC_AUTO_LINK_USERS` | `false` | Tautkan identitas OIDC ke pengguna lokal yang ada jika alamat email cocok. |
|
||||
| `OIDC_PROVIDER_NAME` | | Nama tampilan yang ditunjukkan pada tombol login (mis. "Keycloak", "Google"). Jika kosong, tombol bertuliskan "SSO". |
|
||||
| `OIDC_CLOCK_TOLERANCE` | `30` | Toleransi selisih jam dalam detik untuk validasi token. |
|
||||
| `OIDC_USERNAME_CLAIM` | `preferred_username` | Klaim ID token yang digunakan sebagai username untuk akun baru. |
|
||||
| `EXTERNAL_URL` | | URL publik tempat SnapOtter dapat dijangkau. Diperlukan agar OIDC membangun redirect URI yang benar. |
|
||||
| `COOKIE_SECRET` | dihasilkan otomatis | Secret untuk menandatangani cookie sesi. Setel ini secara eksplisit saat menjalankan beberapa replika. |
|
||||
|
||||
## Panduan penyedia {#provider-guides}
|
||||
|
||||
### Keycloak {#keycloak}
|
||||
|
||||
1. Buat realm baru (atau gunakan yang sudah ada).
|
||||
2. Buka **Clients** dan buat client baru:
|
||||
- **Client ID**: `snapotter`
|
||||
- **Client authentication**: On (confidential)
|
||||
- **Authentication flow**: Standard flow (Authorization Code)
|
||||
3. Di bawah tab **Settings** client, setel **Valid redirect URIs** ke URL callback Anda (mis. `https://photos.example.com/api/auth/oidc/callback`).
|
||||
4. Salin **Client secret** dari tab **Credentials**.
|
||||
5. Setel `OIDC_ISSUER_URL` ke `https://keycloak.example.com/realms/your-realm`.
|
||||
|
||||
### Authentik {#authentik}
|
||||
|
||||
1. Di antarmuka admin, buka **Applications > Providers** dan buat **OAuth2/OpenID Provider** baru.
|
||||
- **Client type**: Confidential
|
||||
- **Redirect URIs**: URL callback Anda
|
||||
- **Signing key**: Pilih key yang ada atau buat satu
|
||||
2. Buat sebuah **Application** dan tautkan ke penyedia.
|
||||
3. Salin **Client ID** dan **Client Secret** dari pengaturan penyedia.
|
||||
4. Setel `OIDC_ISSUER_URL` ke `https://authentik.example.com/application/o/snapotter/` (garis miring di akhir penting).
|
||||
|
||||
### Google {#google}
|
||||
|
||||
1. Buka [Google Cloud Console](https://console.cloud.google.com/).
|
||||
2. Buat proyek (atau pilih yang sudah ada).
|
||||
3. Navigasi ke **APIs & Services > OAuth consent screen** dan konfigurasikan.
|
||||
4. Buka **APIs & Services > Credentials** dan buat **OAuth 2.0 Client ID**:
|
||||
- **Application type**: Web application
|
||||
- **Authorized redirect URIs**: URL callback Anda
|
||||
5. Salin **Client ID** dan **Client secret**.
|
||||
6. Setel `OIDC_ISSUER_URL` ke `https://accounts.google.com`.
|
||||
7. Setel `OIDC_USERNAME_CLAIM` ke `email` (Google tidak menyediakan `preferred_username`).
|
||||
|
||||
## Provisioning pengguna {#user-provisioning}
|
||||
|
||||
### Buat otomatis {#auto-create}
|
||||
|
||||
Saat `OIDC_AUTO_CREATE_USERS` bernilai `true` (default), akun pengguna lokal dibuat pertama kali seseorang login via OIDC. Username diambil dari klaim yang ditentukan oleh `OIDC_USERNAME_CLAIM`, dan peran disetel ke `OIDC_DEFAULT_ROLE`.
|
||||
|
||||
Jika terjadi tabrakan username, sufiks numerik ditambahkan (mis. `jane` menjadi `jane_2`).
|
||||
|
||||
### Tautan otomatis {#auto-link}
|
||||
|
||||
Saat `OIDC_AUTO_LINK_USERS` bernilai `true`, SnapOtter menautkan identitas OIDC ke akun lokal yang ada jika alamat email cocok. Ini berguna saat Anda telah membuat akun pengguna sebelumnya dan ingin mereka mulai menggunakan SSO tanpa kehilangan data.
|
||||
|
||||
::: warning
|
||||
Hanya aktifkan tautan otomatis jika Anda memercayai penyedia OIDC Anda untuk memverifikasi alamat email. Email yang tidak terverifikasi dapat memungkinkan seseorang mengambil alih akun pengguna lain.
|
||||
:::
|
||||
|
||||
### Menonaktifkan login lokal {#disabling-local-login}
|
||||
|
||||
OIDC tidak menonaktifkan login username/kata sandi lokal. Kedua metode tetap tersedia. Admin masih dapat login dengan kredensial lokal jika penyedia OIDC tidak dapat dijangkau.
|
||||
|
||||
## Sertifikat self-signed {#self-signed-certificates}
|
||||
|
||||
Jika penyedia OIDC Anda menggunakan sertifikat self-signed atau CA privat, mount CA bundle ke dalam container dan arahkan `NODE_EXTRA_CA_CERTS` ke sana:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest
|
||||
volumes:
|
||||
- ./my-ca.pem:/etc/ssl/certs/custom-ca.pem:ro
|
||||
environment:
|
||||
NODE_EXTRA_CA_CERTS: /etc/ssl/certs/custom-ca.pem
|
||||
OIDC_ENABLED: "true"
|
||||
OIDC_ISSUER_URL: "https://auth.internal.example.com/realms/myrealm"
|
||||
OIDC_CLIENT_ID: "snapotter"
|
||||
OIDC_CLIENT_SECRET: "your-secret-here"
|
||||
```
|
||||
|
||||
::: danger
|
||||
Jangan setel `NODE_TLS_REJECT_UNAUTHORIZED=0`. Ini menonaktifkan semua verifikasi TLS dan merupakan risiko keamanan.
|
||||
:::
|
||||
|
||||
## Pemecahan masalah {#troubleshooting}
|
||||
|
||||
### Ketidakcocokan redirect URI {#redirect-uri-mismatch}
|
||||
|
||||
Kesalahan paling umum. Periksa perbedaan berikut antara apa yang diharapkan penyedia Anda dan apa yang dikirim SnapOtter:
|
||||
|
||||
- `http` vs `https` - skema harus cocok persis
|
||||
- Garis miring di akhir - beberapa penyedia ketat soal ini
|
||||
- Nomor port - sertakan port jika non-standar
|
||||
- Path - harus `/api/auth/oidc/callback`
|
||||
|
||||
Periksa ulang `EXTERNAL_URL`. Nilainya harus cocok dengan URL yang diketik pengguna di browser mereka.
|
||||
|
||||
### UNABLE_TO_VERIFY_LEAF_SIGNATURE {#unable-to-verify-leaf-signature}
|
||||
|
||||
Penyedia OIDC menggunakan sertifikat yang tidak dipercaya oleh Node.js. Lihat [Sertifikat self-signed](#self-signed-certificates) di atas.
|
||||
|
||||
### Kesalahan selisih jam {#clock-skew-errors}
|
||||
|
||||
Jika jam server Anda dan jam penyedia OIDC tidak sinkron, validasi token bisa gagal. Naikkan `OIDC_CLOCK_TOLERANCE` (default 30 detik). Perbaikan yang lebih baik adalah menjalankan NTP di kedua mesin.
|
||||
|
||||
### "OIDC provider unreachable" {#oidc-provider-unreachable}
|
||||
|
||||
SnapOtter mengambil dokumen discovery penyedia saat startup dan selama login. Periksa:
|
||||
|
||||
- Resolusi DNS dari dalam container Docker (`docker exec snapotter nslookup auth.example.com`)
|
||||
- Aturan firewall antara container dan penyedia
|
||||
- Nilai `OIDC_ISSUER_URL` - harus dapat dijangkau dari server, bukan hanya dari browser Anda
|
||||
|
||||
### Klaim hilang {#missing-claims}
|
||||
|
||||
Jika username atau email kosong setelah login, penyedia Anda mungkin tidak mengembalikan klaim yang diharapkan. Verifikasi:
|
||||
|
||||
- Scope yang dikonfigurasi di `OIDC_SCOPES` menyertakan `profile` dan `email`
|
||||
- Penyedia dikonfigurasi untuk menyertakan klaim yang ditentukan di `OIDC_USERNAME_CLAIM` dalam ID token
|
||||
- Beberapa penyedia memerlukan konfigurasi mapper/scope eksplisit untuk merilis klaim
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
description: "Siapkan Single Sign-On SAML 2.0 untuk SnapOtter. Panduan langkah demi langkah untuk Okta, Azure AD / Entra ID, Google Workspace, dan penyedia identitas SAML lainnya."
|
||||
i18n_source_hash: 33dfb8b02a22
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: becd9d2f8770
|
||||
---
|
||||
|
||||
# SAML SSO {#saml-sso}
|
||||
|
||||
SnapOtter mendukung SAML 2.0 untuk single sign-on. Pengguna dapat login via penyedia identitas eksternal (Okta, Azure AD / Entra ID, Google Workspace, atau IdP SAML 2.0 standar apa pun) alih-alih autentikasi username/kata sandi lokal.
|
||||
|
||||
::: tip Fitur enterprise
|
||||
SAML SSO memerlukan lisensi **team** atau **enterprise** dengan fitur `saml_sso`. Jika `SAML_ENABLED=true` disetel tanpa lisensi yang valid, rute SAML dilewati secara diam-diam dan peringatan dicatat.
|
||||
:::
|
||||
|
||||
## Prasyarat {#prerequisites}
|
||||
|
||||
- Instance SnapOtter yang berjalan dan dapat dijangkau di URL publik
|
||||
- `EXTERNAL_URL` disetel ke URL publik itu (mis. `https://photos.example.com`)
|
||||
- Kunci lisensi team atau enterprise dengan fitur `saml_sso`
|
||||
- Akses admin ke penyedia identitas SAML Anda
|
||||
|
||||
## Mulai cepat {#quick-start}
|
||||
|
||||
Tambahkan variabel lingkungan ini ke `docker-compose.yml` Anda:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
snapotter:
|
||||
image: snapotter/snapotter:latest
|
||||
environment:
|
||||
EXTERNAL_URL: "https://photos.example.com"
|
||||
SNAPOTTER_LICENSE_KEY: "your-license-key"
|
||||
SAML_ENABLED: "true"
|
||||
SAML_IDP_SSO_URL: "https://idp.example.com/sso/saml"
|
||||
SAML_IDP_CERTIFICATE: |
|
||||
MIICpDCCAYwCCQDU+pQ4pHgSpDANBgkqhkiG9w0BAQsFADAUMRIw
|
||||
...your IdP's signing certificate in PEM format...
|
||||
EAYHKoZIzj0CAQYFK4EEACIDYgAE
|
||||
```
|
||||
|
||||
Mulai ulang container. Tombol "Sign in with SAML" (atau label yang disetel oleh `SAML_PROVIDER_NAME`) muncul di halaman login.
|
||||
|
||||
## Referensi konfigurasi {#configuration-reference}
|
||||
|
||||
| Variabel | Default | Deskripsi |
|
||||
|---|---|---|
|
||||
| `SAML_ENABLED` | `false` | Aktifkan login SAML. |
|
||||
| `SAML_IDP_SSO_URL` | | URL endpoint SSO IdP. **Wajib** saat SAML diaktifkan. |
|
||||
| `SAML_IDP_CERTIFICATE` | | Sertifikat penandatanganan X.509 IdP dalam format PEM (teks sertifikat itu sendiri, bukan path file). **Wajib** saat SAML diaktifkan. |
|
||||
| `EXTERNAL_URL` | | URL publik tempat SnapOtter dapat dijangkau. **Wajib** saat SAML diaktifkan. |
|
||||
| `SAML_ENTITY_ID` | `${EXTERNAL_URL}/api/auth/saml/metadata` | SP Entity ID / Audience URI yang dikirim ke IdP. |
|
||||
| `SAML_CALLBACK_URL` | `${EXTERNAL_URL}/api/auth/saml/callback` | URL Assertion Consumer Service (ACS). |
|
||||
| `SAML_AUTO_CREATE_USERS` | `true` | Buat akun pengguna lokal secara otomatis saat login SAML pertama. |
|
||||
| `SAML_AUTO_LINK_USERS` | `false` | Tautkan identitas SAML ke pengguna lokal yang ada jika alamat email cocok. |
|
||||
| `SAML_DEFAULT_ROLE` | `user` | Peran yang diberikan ke pengguna SAML yang dibuat otomatis. Salah satu dari `admin`, `editor`, atau `user`. |
|
||||
| `SAML_PROVIDER_NAME` | | Label tampilan untuk tombol login SAML di frontend (mis. "Okta", "Azure AD"). Jika kosong, tombol bertuliskan "SAML". |
|
||||
| `SAML_USERNAME_ATTRIBUTE` | | Atribut assertion SAML yang digunakan sebagai username. Jika kosong, kembali ke local-part email, lalu NameID. |
|
||||
| `SAML_EMAIL_ATTRIBUTE` | `email` | Atribut assertion SAML yang digunakan sebagai alamat email pengguna. |
|
||||
|
||||
Server menolak untuk mulai jika `SAML_ENABLED=true` dan salah satu dari tiga variabel yang wajib (`SAML_IDP_SSO_URL`, `SAML_IDP_CERTIFICATE`, `EXTERNAL_URL`) tidak ada.
|
||||
|
||||
::: details Catatan keamanan
|
||||
Baik `wantAuthnResponseSigned` maupun `wantAssertionsSigned` dikodekan tetap (hardcoded) ke `true`. SnapOtter menolak respons SAML yang tidak ditandatangani atau ditandatangani secara tidak benar. Assertion dari IdP tepercaya diperlakukan sebagai email-terverifikasi.
|
||||
|
||||
Hanya login yang diinisiasi SP yang didukung. SnapOtter tidak mendukung login yang diinisiasi IdP (tidak diminta) atau Single Logout (SLO). Logout dari SnapOtter tidak membuat pengguna keluar dari IdP.
|
||||
:::
|
||||
|
||||
## Metadata dan URL SP {#sp-metadata-and-urls}
|
||||
|
||||
IdP Anda membutuhkan tiga nilai dari SnapOtter:
|
||||
|
||||
| Bidang | Nilai |
|
||||
|---|---|
|
||||
| **ACS URL** (Assertion Consumer Service) | `${EXTERNAL_URL}/api/auth/saml/callback` |
|
||||
| **Entity ID** / **Audience URI** | `${EXTERNAL_URL}/api/auth/saml/metadata` |
|
||||
| **SP Metadata** (XML) | `GET ${EXTERNAL_URL}/api/auth/saml/metadata` |
|
||||
|
||||
Misalnya, jika `EXTERNAL_URL` adalah `https://photos.example.com`:
|
||||
|
||||
- ACS URL: `https://photos.example.com/api/auth/saml/callback`
|
||||
- Entity ID: `https://photos.example.com/api/auth/saml/metadata`
|
||||
- Endpoint metadata: `https://photos.example.com/api/auth/saml/metadata` (mengembalikan XML)
|
||||
|
||||
Beberapa IdP dapat mengimpor URL metadata SP secara langsung, yang mengisi otomatis ACS URL dan Entity ID.
|
||||
|
||||
## Penyiapan penyedia {#provider-setup}
|
||||
|
||||
### Okta {#okta}
|
||||
|
||||
1. Di konsol admin Okta, buka **Applications > Create App Integration**.
|
||||
2. Pilih **SAML 2.0** dan klik **Next**.
|
||||
3. Setel nama (mis. "SnapOtter") dan klik **Next**.
|
||||
4. Konfigurasikan pengaturan SAML:
|
||||
- **Single sign-on URL**: ACS URL Anda (mis. `https://photos.example.com/api/auth/saml/callback`)
|
||||
- **Audience URI (SP Entity ID)**: Entity ID Anda (mis. `https://photos.example.com/api/auth/saml/metadata`)
|
||||
- **Name ID format**: EmailAddress
|
||||
- **Application username**: Email
|
||||
5. Di bawah **Attribute Statements**, tambahkan `email` yang dipetakan ke `user.email`.
|
||||
6. Klik **Next**, lalu **Finish**.
|
||||
7. Buka tab **Sign On**, klik **View SAML setup instructions**, dan salin:
|
||||
- **Identity Provider Single Sign-On URL** ke `SAML_IDP_SSO_URL`
|
||||
- **X.509 Certificate** ke `SAML_IDP_CERTIFICATE`
|
||||
|
||||
### Azure AD / Entra ID {#azure-ad-entra-id}
|
||||
|
||||
1. Di portal Azure, buka **Microsoft Entra ID > Enterprise applications > New application**.
|
||||
2. Klik **Create your own application**, beri nama "SnapOtter", dan pilih **Integrate any other application you don't find in the gallery**.
|
||||
3. Buka **Single sign-on > SAML** dan klik **Edit** pada bagian **Basic SAML Configuration**:
|
||||
- **Identifier (Entity ID)**: Entity ID Anda (mis. `https://photos.example.com/api/auth/saml/metadata`)
|
||||
- **Reply URL (ACS URL)**: ACS URL Anda (mis. `https://photos.example.com/api/auth/saml/callback`)
|
||||
4. Di bawah **SAML Certificates**, unduh **Certificate (Base64)**.
|
||||
5. Di bawah **Set up SnapOtter**, salin **Login URL**.
|
||||
6. Setel `SAML_IDP_SSO_URL` ke Login URL dan `SAML_IDP_CERTIFICATE` ke isi sertifikat yang diunduh.
|
||||
7. Tetapkan pengguna atau grup ke aplikasi di bawah **Users and groups**.
|
||||
|
||||
### Google Workspace {#google-workspace}
|
||||
|
||||
1. Di konsol Google Admin, buka **Apps > Web and mobile apps > Add app > Add custom SAML app**.
|
||||
2. Beri nama aplikasi "SnapOtter" dan klik **Continue**.
|
||||
3. Di halaman **Google Identity Provider details**, salin **SSO URL** dan unduh **Certificate**. Klik **Continue**.
|
||||
4. Konfigurasikan detail Service Provider:
|
||||
- **ACS URL**: ACS URL Anda (mis. `https://photos.example.com/api/auth/saml/callback`)
|
||||
- **Entity ID**: Entity ID Anda (mis. `https://photos.example.com/api/auth/saml/metadata`)
|
||||
- **Name ID format**: EMAIL
|
||||
- **Name ID**: Basic Information > Primary email
|
||||
5. Klik **Continue**, lalu **Finish**.
|
||||
6. Aktifkan aplikasi **ON** untuk unit organisasi Anda.
|
||||
7. Setel `SAML_IDP_SSO_URL` ke SSO URL dari langkah 3 dan `SAML_IDP_CERTIFICATE` ke isi sertifikat yang diunduh.
|
||||
|
||||
### IdP SAML 2.0 generik {#generic-saml-2-0-idp}
|
||||
|
||||
Untuk penyedia identitas apa pun yang patuh SAML 2.0:
|
||||
|
||||
1. Buat aplikasi/service provider SAML baru di IdP Anda.
|
||||
2. Setel **ACS URL** ke `${EXTERNAL_URL}/api/auth/saml/callback`.
|
||||
3. Setel **Entity ID** / **Audience** ke `${EXTERNAL_URL}/api/auth/saml/metadata`.
|
||||
4. Konfigurasikan IdP untuk mengirim email pengguna dalam atribut bernama `email` (atau setel `SAML_EMAIL_ATTRIBUTE` agar cocok dengan nama atribut IdP Anda).
|
||||
5. Salin **IdP SSO URL** dan **sertifikat penandatanganan** ke `SAML_IDP_SSO_URL` dan `SAML_IDP_CERTIFICATE`.
|
||||
|
||||
## Provisioning pengguna {#user-provisioning}
|
||||
|
||||
### Buat otomatis {#auto-create}
|
||||
|
||||
Saat `SAML_AUTO_CREATE_USERS` bernilai `true` (default), akun pengguna lokal dibuat pertama kali seseorang login via SAML. Peran disetel ke `SAML_DEFAULT_ROLE`.
|
||||
|
||||
Username diturunkan dalam urutan ini:
|
||||
|
||||
1. Nilai atribut assertion yang ditentukan oleh `SAML_USERNAME_ATTRIBUTE` (jika disetel dan ada)
|
||||
2. Local-part alamat email (semua yang sebelum `@`)
|
||||
3. NameID SAML
|
||||
|
||||
Jika terjadi tabrakan username, sufiks numerik ditambahkan (mis. `jane` menjadi `jane_2`).
|
||||
|
||||
### Tautan otomatis {#auto-link}
|
||||
|
||||
Saat `SAML_AUTO_LINK_USERS` bernilai `true`, SnapOtter menautkan identitas SAML ke akun lokal yang ada jika alamat email cocok. Ini berguna saat Anda telah membuat akun pengguna sebelumnya dan ingin mereka mulai menggunakan SSO tanpa kehilangan data.
|
||||
|
||||
::: warning
|
||||
Hanya aktifkan tautan otomatis jika Anda memercayai IdP SAML Anda untuk memverifikasi alamat email. Email yang tidak terverifikasi dari IdP yang salah konfigurasi dapat memungkinkan seseorang mengambil alih akun pengguna lain.
|
||||
:::
|
||||
|
||||
### Pemetaan atribut {#attribute-mapping}
|
||||
|
||||
| Bidang SnapOtter | Sumber | Konfigurasi |
|
||||
|---|---|---|
|
||||
| Email | Atribut assertion | `SAML_EMAIL_ATTRIBUTE` (default: `email`) |
|
||||
| Username | Atribut assertion, email, atau NameID | `SAML_USERNAME_ATTRIBUTE` (lihat urutan penurunan di atas) |
|
||||
| External ID | NameID | Selalu NameID SAML, tidak dapat dikonfigurasi |
|
||||
|
||||
## Penegakan SSO {#sso-enforcement}
|
||||
|
||||
Jika Anda ingin mewajibkan semua pengguna login via SAML (atau OIDC) dan memblokir login kata sandi lokal, aktifkan penegakan SSO:
|
||||
|
||||
1. Pastikan fitur enterprise `sso_enforcement` dilisensikan (tersedia pada paket team dan enterprise).
|
||||
2. Di **Admin Settings > Security**, aktifkan **SSO Enforcement**.
|
||||
3. Setel **break-glass username**: ini adalah satu-satunya akun lokal yang masih dapat login dengan kata sandi, untuk akses darurat jika IdP tidak dapat dijangkau.
|
||||
|
||||
Saat penegakan SSO aktif, setiap upaya login lokal (kecuali untuk pengguna break-glass) mengembalikan kesalahan 403 dengan pesan "Local password login is disabled. Please use SSO."
|
||||
|
||||
::: tip
|
||||
Selalu konfigurasikan break-glass username sebelum mengaktifkan penegakan SSO. Tanpanya, Anda bisa terkunci dari SnapOtter jika IdP Anda mati.
|
||||
:::
|
||||
|
||||
## Menggunakan SAML bersama OIDC {#using-saml-alongside-oidc}
|
||||
|
||||
SAML dan OIDC dapat diaktifkan secara bersamaan. Saat keduanya aktif, halaman login menampilkan tombol terpisah untuk setiap penyedia (diberi label oleh `SAML_PROVIDER_NAME` dan `OIDC_PROVIDER_NAME`). Pengguna dapat login dengan metode mana pun.
|
||||
|
||||
Kedua penyedia berbagi pengaturan buat-otomatis, tautan-otomatis, dan penegakan SSO yang sama secara independen: masing-masing memiliki variabel `*_AUTO_CREATE_USERS`, `*_AUTO_LINK_USERS`, dan `*_DEFAULT_ROLE` sendiri.
|
||||
|
||||
## Pemecahan masalah {#troubleshooting}
|
||||
|
||||
### Validasi assertion gagal {#assertion-validation-failed}
|
||||
|
||||
Tanda tangan respons SAML atau tanda tangan assertion tidak dapat diverifikasi. Periksa:
|
||||
|
||||
- Sertifikat di `SAML_IDP_CERTIFICATE` cocok dengan sertifikat penandatanganan saat ini di IdP Anda (sertifikat berotasi, jadi periksa kedaluwarsa)
|
||||
- Sertifikat dalam format PEM (dimulai dengan `-----BEGIN CERTIFICATE-----`)
|
||||
- Sertifikat adalah teks lengkap, bukan path file
|
||||
- ACS URL dan Entity ID yang dikonfigurasi di IdP Anda cocok persis dengan nilai SnapOtter (skema, host, port, path)
|
||||
|
||||
### Atribut hilang {#missing-attributes}
|
||||
|
||||
Jika username atau email kosong setelah login, IdP Anda mungkin tidak mengirim atribut yang diharapkan. Periksa:
|
||||
|
||||
- IdP Anda dikonfigurasi untuk merilis atribut `email` (atau apa pun yang disetel di `SAML_EMAIL_ATTRIBUTE`)
|
||||
- Jika menggunakan `SAML_USERNAME_ATTRIBUTE`, verifikasi bahwa atribut itu disertakan dalam assertion
|
||||
- Beberapa IdP memerlukan konfigurasi pemetaan atribut eksplisit sebelum merilis klaim
|
||||
|
||||
### Selisih jam {#clock-skew}
|
||||
|
||||
Assertion SAML menyertakan kondisi stempel waktu (`NotBefore`, `NotOnOrAfter`). Jika jam server Anda dan jam IdP tidak sinkron, validasi assertion gagal. Jalankan NTP di kedua mesin untuk menjaga jam tetap selaras.
|
||||
|
||||
### "SAML is enabled via env but saml_sso enterprise feature is not licensed" {#saml-is-enabled-via-env-but-saml-sso-enterprise-feature-is-not-licensed}
|
||||
|
||||
Peringatan ini muncul di log server saat `SAML_ENABLED=true` tetapi lisensi tidak menyertakan fitur `saml_sso`. Verifikasi kunci lisensi dan paket Anda. Fitur `saml_sso` tersedia pada paket team dan enterprise.
|
||||
|
||||
### Login mengalihkan kembali dengan kesalahan {#login-redirects-back-with-error}
|
||||
|
||||
Jika mengklik tombol login SAML mengalihkan kembali ke halaman login dengan kesalahan, periksa log server untuk detail. Penyebab umum:
|
||||
|
||||
- IdP SSO URL tidak dapat dijangkau dari server
|
||||
- IdP menolak permintaan autentikasi (periksa log audit IdP)
|
||||
- IdP mengembalikan respons yang tidak ditandatangani (SnapOtter memerlukan baik respons maupun assertion untuk ditandatangani)
|
||||
@@ -0,0 +1,298 @@
|
||||
---
|
||||
description: "Siapkan provisioning SCIM 2.0 untuk menyinkronkan pengguna dan grup dari penyedia identitas Anda ke SnapOtter. Mencakup Okta, Azure AD / Entra ID, dan integrasi kustom."
|
||||
i18n_source_hash: bbd50119ec12
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: a7f596c5fea9
|
||||
---
|
||||
|
||||
# Provisioning SCIM {#scim-provisioning}
|
||||
|
||||
SnapOtter mengimplementasikan SCIM 2.0 (System for Cross-domain Identity Management) untuk provisioning pengguna dan grup secara otomatis. Penyedia identitas Anda dapat membuat, memperbarui, menonaktifkan, dan mengaktifkan kembali akun pengguna serta menyinkronkan keanggotaan grup secara otomatis.
|
||||
|
||||
::: tip Fitur enterprise
|
||||
Provisioning SCIM memerlukan lisensi **enterprise** dengan fitur `scim`. Fitur ini tidak tersedia pada paket team. Tanpa fitur tersebut, semua endpoint SCIM (kecuali discovery) mengembalikan 403.
|
||||
:::
|
||||
|
||||
## Prasyarat {#prerequisites}
|
||||
|
||||
- Sebuah instance SnapOtter yang berjalan dan dapat dijangkau melalui URL publik
|
||||
- Kunci lisensi enterprise dengan fitur `scim`
|
||||
- Akses admin ke SnapOtter (izin `users:manage` diperlukan untuk membuat atau mencabut token SCIM)
|
||||
- Akses admin ke pengaturan provisioning penyedia identitas Anda
|
||||
|
||||
## Mulai cepat {#quick-start}
|
||||
|
||||
1. Buat token bearer SCIM:
|
||||
|
||||
```bash
|
||||
curl -X POST https://photos.example.com/api/v1/enterprise/scim/token \
|
||||
-H "Cookie: snapotter-session=YOUR_SESSION" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
Respons berisi token tersebut. Simpan segera; token tidak dapat diambil kembali.
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "a1b2c3d4e5f6...",
|
||||
"message": "Save this token - it cannot be retrieved again"
|
||||
}
|
||||
```
|
||||
|
||||
2. Pada penyedia identitas Anda, konfigurasikan provisioning SCIM dengan:
|
||||
- **Base URL**: `https://photos.example.com/api/v1/scim/v2`
|
||||
- **Authentication**: Bearer token (tempel token dari langkah 1)
|
||||
|
||||
## Autentikasi {#authentication}
|
||||
|
||||
Endpoint SCIM menggunakan Bearer token khusus, terpisah dari sesi pengguna dan API key.
|
||||
|
||||
### Membuat token {#generating-a-token}
|
||||
|
||||
`POST /api/v1/enterprise/scim/token` membuat token SCIM baru. Endpoint ini memerlukan sesi valid dengan izin `users:manage`.
|
||||
|
||||
Token dikembalikan dalam bentuk teks biasa tepat satu kali. SnapOtter hanya menyimpan hash scrypt. Jika Anda kehilangan token, cabut token tersebut dan buat yang baru.
|
||||
|
||||
Hanya satu token SCIM yang aktif pada satu waktu. Membuat token baru akan menggantikan token sebelumnya.
|
||||
|
||||
### Mencabut token {#revoking-a-token}
|
||||
|
||||
`DELETE /api/v1/enterprise/scim/token` mencabut token SCIM saat ini. Endpoint ini juga memerlukan `users:manage`.
|
||||
|
||||
### Pembatasan laju {#rate-limiting}
|
||||
|
||||
Endpoint SCIM dibatasi hingga 1000 permintaan per menit per token. Melebihi batas ini mengembalikan HTTP 429.
|
||||
|
||||
## Resource yang didukung {#supported-resources}
|
||||
|
||||
| Resource SCIM | Konsep SnapOtter | Create | Read | Update | Delete |
|
||||
|---|---|---|---|---|---|
|
||||
| User | Akun pengguna | Ya | Ya | Ya | Soft delete |
|
||||
| Group | Team | Ya | Ya | Ya | Ya |
|
||||
|
||||
::: warning
|
||||
SCIM Group dipetakan ke **team** SnapOtter, bukan role. SCIM tidak dapat mengatur role seorang pengguna. Semua pengguna yang dibuat melalui SCIM diberi role `user`. Untuk mengubah role pengguna, gunakan UI admin SnapOtter.
|
||||
:::
|
||||
|
||||
## Operasi pengguna {#user-operations}
|
||||
|
||||
### Buat pengguna {#create-user}
|
||||
|
||||
`POST /api/v1/scim/v2/Users`
|
||||
|
||||
Membuat akun pengguna baru dengan `authProvider` diatur ke `scim` dan role `user`. Pengguna ditugaskan ke team Default. Jika `active` bernilai `false`, role diatur ke `disabled` sebagai gantinya.
|
||||
|
||||
Atribut wajib: `userName`. Opsional: `externalId`, `emails`, `active` (default `true`).
|
||||
|
||||
### Daftar dan filter pengguna {#list-and-filter-users}
|
||||
|
||||
`GET /api/v1/scim/v2/Users`
|
||||
|
||||
Mengembalikan daftar pengguna dengan paginasi. Mendukung parameter kueri `startIndex` dan `count` (maksimum 200 hasil per halaman).
|
||||
|
||||
Filtering hanya mendukung `eq` (equals), pada atribut berikut:
|
||||
|
||||
- `userName eq "jane"`
|
||||
- `externalId eq "ext-12345"`
|
||||
|
||||
Operator filter dan atribut lain mengembalikan HTTP 400.
|
||||
|
||||
### Ambil pengguna {#get-user}
|
||||
|
||||
`GET /api/v1/scim/v2/Users/:id`
|
||||
|
||||
Mengembalikan satu pengguna berdasarkan ID pengguna SnapOtter mereka.
|
||||
|
||||
### Ganti pengguna {#replace-user}
|
||||
|
||||
`PUT /api/v1/scim/v2/Users/:id`
|
||||
|
||||
Mengganti atribut pengguna. Mendukung `userName`, `externalId`, `emails`, dan `active`. Perubahan username diperiksa untuk konflik (409 jika username baru sudah dipakai pengguna lain).
|
||||
|
||||
### Patch pengguna {#patch-user}
|
||||
|
||||
`PATCH /api/v1/scim/v2/Users/:id`
|
||||
|
||||
Pembaruan sebagian menggunakan SCIM PatchOp. Operasi yang didukung:
|
||||
|
||||
| Operasi | Path |
|
||||
|---|---|
|
||||
| `replace` | `active`, `userName`, `externalId`, `emails`, `emails[type eq "work"].value`, `name.formatted`, `displayName` |
|
||||
| `add` | Sama seperti `replace` |
|
||||
| `remove` | `externalId`, `emails` |
|
||||
|
||||
Path `name.formatted` dan `displayName` diterima demi kompatibilitas tetapi tidak memiliki efek persisten (SnapOtter tidak menyimpan display name terpisah).
|
||||
|
||||
Operasi `replace` tanpa nilai (di mana value adalah objek tanpa `path`) juga didukung, dengan kunci `userName`, `externalId`, `emails`, dan `active`.
|
||||
|
||||
### Nonaktifkan pengguna (soft delete) {#deactivate-user-soft-delete}
|
||||
|
||||
`DELETE /api/v1/scim/v2/Users/:id`
|
||||
|
||||
SnapOtter tidak menghapus pengguna secara permanen melalui SCIM. Sebaliknya, DELETE melakukan deaktivasi lunak:
|
||||
|
||||
1. Role pengguna diubah dari nilai saat ini (mis. `editor`) menjadi `disabled:editor`, sambil menyimpan role aslinya.
|
||||
2. Kata sandi pengguna dihapus.
|
||||
3. Semua sesi aktif dicabut.
|
||||
4. Semua API key dicabut.
|
||||
|
||||
Pengguna tidak lagi dapat masuk atau menggunakan API key mana pun. Data mereka (file, riwayat) tetap dipertahankan.
|
||||
|
||||
### Aktifkan kembali pengguna {#reactivate-user}
|
||||
|
||||
Untuk mengaktifkan kembali pengguna yang sebelumnya dinonaktifkan, kirim permintaan `PUT` atau `PATCH` dengan `active: true`. SnapOtter memulihkan role asli dari sebelum deaktivasi (mis. `disabled:editor` menjadi `editor` lagi). Jika role asli tidak dapat ditentukan, akan kembali ke `user`.
|
||||
|
||||
::: details Contoh: nonaktifkan dan aktifkan kembali melalui PATCH
|
||||
```json
|
||||
// Deactivate
|
||||
{
|
||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations": [
|
||||
{ "op": "replace", "path": "active", "value": false }
|
||||
]
|
||||
}
|
||||
|
||||
// Reactivate
|
||||
{
|
||||
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations": [
|
||||
{ "op": "replace", "path": "active", "value": true }
|
||||
]
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
## Operasi grup {#group-operations}
|
||||
|
||||
SCIM Group dipetakan ke team SnapOtter. Membuat grup akan membuat team. Keanggotaan grup mengontrol team mana yang menjadi milik seorang pengguna.
|
||||
|
||||
### Buat grup {#create-group}
|
||||
|
||||
`POST /api/v1/scim/v2/Groups`
|
||||
|
||||
Wajib: `displayName`. Opsional: `members` (array dari `{ value: userId }`).
|
||||
|
||||
### Daftar dan filter grup {#list-and-filter-groups}
|
||||
|
||||
`GET /api/v1/scim/v2/Groups`
|
||||
|
||||
Filtering hanya mendukung `displayName eq "..."`. Dengan paginasi `startIndex` dan `count` (maksimum 200 hasil per halaman).
|
||||
|
||||
### Ambil grup {#get-group}
|
||||
|
||||
`GET /api/v1/scim/v2/Groups/:id`
|
||||
|
||||
### Ganti grup {#replace-group}
|
||||
|
||||
`PUT /api/v1/scim/v2/Groups/:id`
|
||||
|
||||
Mengganti nama grup dan seluruh daftar keanggotaan. Anggota yang ada namun tidak ada dalam daftar baru dipindahkan ke team Default.
|
||||
|
||||
### Patch grup {#patch-group}
|
||||
|
||||
`PATCH /api/v1/scim/v2/Groups/:id`
|
||||
|
||||
Mendukung operasi berikut:
|
||||
|
||||
| Operasi | Path | Efek |
|
||||
|---|---|---|
|
||||
| `add` | `members` | Menambahkan pengguna ke team |
|
||||
| `remove` | `members[value eq "userId"]` | Memindahkan pengguna ke team Default |
|
||||
| `replace` | `displayName` | Mengubah nama team |
|
||||
| `replace` | `members` | Mengganti semua anggota (anggota yang dihapus dipindahkan ke team Default) |
|
||||
|
||||
### Hapus grup {#delete-group}
|
||||
|
||||
`DELETE /api/v1/scim/v2/Groups/:id`
|
||||
|
||||
Menghapus team. Semua anggota team yang dihapus dipindahkan ke team Default. Pengguna tidak dinonaktifkan atau dihapus.
|
||||
|
||||
## Penyiapan IdP {#idp-setup}
|
||||
|
||||
### Okta {#okta}
|
||||
|
||||
1. Di konsol admin Okta, buka aplikasi SnapOtter Anda (atau buat baru).
|
||||
2. Buka tab **Provisioning** dan klik **Configure API Integration**.
|
||||
3. Centang **Enable API Integration** dan masukkan:
|
||||
- **Base URL**: `https://photos.example.com/api/v1/scim/v2`
|
||||
- **API Token**: Token bearer SCIM yang dibuat di atas
|
||||
4. Klik **Test API Credentials**, lalu **Save**.
|
||||
5. Di bawah **Provisioning > To App**, aktifkan:
|
||||
- **Create Users**
|
||||
- **Update User Attributes**
|
||||
- **Deactivate Users**
|
||||
6. Di bawah **Push Groups**, konfigurasikan grup Okta mana yang akan disinkronkan sebagai team SnapOtter.
|
||||
|
||||
### Azure AD / Entra ID {#azure-ad-entra-id}
|
||||
|
||||
1. Di portal Azure, buka aplikasi enterprise SnapOtter Anda.
|
||||
2. Buka **Provisioning** dan atur **Provisioning Mode** ke **Automatic**.
|
||||
3. Di bawah **Admin Credentials**, masukkan:
|
||||
- **Tenant URL**: `https://photos.example.com/api/v1/scim/v2`
|
||||
- **Secret Token**: Token bearer SCIM yang dibuat di atas
|
||||
4. Klik **Test Connection**, lalu **Save**.
|
||||
5. Di bawah **Mappings**, konfigurasikan pemetaan atribut pengguna dan grup. Nilai default biasanya sudah berfungsi, tetapi pastikan `userName` dipetakan ke `userPrincipalName` atau `mail` sesuai keinginan.
|
||||
6. Atur **Provisioning Status** ke **On** dan simpan.
|
||||
|
||||
Azure melakukan provisioning pengguna dan grup pada siklus sinkronisasi tetap (biasanya setiap 40 menit).
|
||||
|
||||
## Endpoint discovery {#discovery-endpoints}
|
||||
|
||||
Ketiga endpoint ini tersedia tanpa autentikasi dan menjelaskan kemampuan server SCIM:
|
||||
|
||||
| Endpoint | Deskripsi |
|
||||
|---|---|
|
||||
| `GET /api/v1/scim/v2/ServiceProviderConfig` | Kemampuan server dan fitur yang didukung |
|
||||
| `GET /api/v1/scim/v2/Schemas` | Definisi skema User dan Group |
|
||||
| `GET /api/v1/scim/v2/ResourceTypes` | Tipe resource yang tersedia (User, Group) |
|
||||
|
||||
`ServiceProviderConfig` mengiklankan kemampuan berikut:
|
||||
|
||||
| Fitur | Didukung |
|
||||
|---|---|
|
||||
| Patch | Ya |
|
||||
| Bulk | Tidak |
|
||||
| Filter | Ya (maks 200 hasil, hanya operator `eq`) |
|
||||
| Change password | Tidak |
|
||||
| Sort | Tidak |
|
||||
| ETag | Tidak |
|
||||
|
||||
## Batasan {#limitations}
|
||||
|
||||
- **Filtering**: Hanya operator `eq` yang didukung. Filter kompleks, operator `and`/`or`, `co` (contains), dan `sw` (starts with) tidak diimplementasikan.
|
||||
- **Operasi bulk**: Tidak didukung.
|
||||
- **Sort dan ETag**: Tidak didukung.
|
||||
- **Role**: SCIM tidak dapat menetapkan role SnapOtter. Semua pengguna yang di-provisioning mendapat role `user`.
|
||||
- **MAX_USERS**: Batas variabel lingkungan `MAX_USERS` tidak diberlakukan pada pembuatan pengguna via SCIM. Jika Anda perlu membatasi jumlah pengguna, kelola penugasan di IdP Anda.
|
||||
- **Satu token**: Hanya satu token SCIM yang dapat aktif pada satu waktu. Jika beberapa IdP membutuhkan akses SCIM, mereka harus berbagi token.
|
||||
- **Grup adalah team**: SCIM Group berhubungan dengan team, bukan role atau grup izin.
|
||||
|
||||
## Pemecahan masalah {#troubleshooting}
|
||||
|
||||
### 403 "SCIM provisioning requires an enterprise license with the scim feature" {#_403-scim-provisioning-requires-an-enterprise-license-with-the-scim-feature}
|
||||
|
||||
Lisensi Anda tidak menyertakan fitur `scim`, atau tidak ada lisensi yang dikonfigurasi. SCIM memerlukan lisensi paket enterprise. Pastikan `SNAPOTTER_LICENSE_KEY` telah diatur dan lisensi menyertakan fitur `scim`.
|
||||
|
||||
### 401 "Bearer token required" {#_401-bearer-token-required}
|
||||
|
||||
Permintaan SCIM tidak menyertakan header `Authorization: Bearer <token>`. Periksa konfigurasi provisioning IdP Anda.
|
||||
|
||||
### 401 "Invalid token" {#_401-invalid-token}
|
||||
|
||||
Token tidak cocok dengan hash yang tersimpan. Ini terjadi jika token dicabut dan dibuat ulang. Perbarui token di pengaturan provisioning IdP Anda.
|
||||
|
||||
### 401 "SCIM not configured" {#_401-scim-not-configured}
|
||||
|
||||
Belum ada token SCIM yang dibuat. Gunakan endpoint `POST /api/v1/enterprise/scim/token` untuk membuatnya.
|
||||
|
||||
### 409 "User already exists" / "userName already taken" {#_409-user-already-exists-username-already-taken}
|
||||
|
||||
Pengguna dengan username yang sama sudah ada. Ini dapat terjadi ketika IdP mencoba ulang pembuatan yang gagal. Periksa username duplikat di panel admin SnapOtter.
|
||||
|
||||
### 429 "SCIM rate limit exceeded" {#_429-scim-rate-limit-exceeded}
|
||||
|
||||
IdP mengirim lebih dari 1000 permintaan per menit. Ini biasanya terjadi selama sinkronisasi awal yang besar. Sebagian besar IdP otomatis mencoba ulang setelah jendela pembatasan laju direset. Jika masalah berlanjut, periksa interval sinkronisasi provisioning IdP Anda.
|
||||
|
||||
### Pengguna di-deprovisioning tetapi tidak dihapus dari UI {#users-deprovisioned-but-not-removed-from-the-ui}
|
||||
|
||||
SCIM DELETE adalah deaktivasi lunak. Pengguna yang dinonaktifkan tetap muncul dalam daftar pengguna admin dengan status dinonaktifkan. Ini disengaja agar data mereka tetap terjaga. Role mereka ditampilkan sebagai `disabled:<original-role>`.
|
||||
@@ -0,0 +1,339 @@
|
||||
---
|
||||
description: "Panduan pengerasan keamanan untuk SnapOtter. Keamanan kontainer, isolasi jaringan, Docker secrets, deployment Kubernetes, dan artefak kepatuhan."
|
||||
i18n_source_hash: 986f7658430c
|
||||
i18n_provenance: machine
|
||||
i18n_output_hash: 75b0e460c514
|
||||
---
|
||||
|
||||
# Keamanan & Pengerasan {#security-hardening}
|
||||
|
||||
SnapOtter memproses file sepenuhnya di infrastruktur Anda. SnapOtter mengirim analitik produk dan laporan crash yang anonim serta bebas konten secara default untuk membantu meningkatkan proyek. SnapOtter tidak pernah mengirim file Anda, nama file, isi file, output OCR, metadata gambar, atau teks dokumen. Umpan balik opsional dikirim hanya setelah pengguna mengirimkannya, hanya ketika analitik diaktifkan, dan bidang kontak disertakan hanya dengan persetujuan kontak eksplisit. Administrator dapat mematikan penangkapan analitik dan umpan balik dalam satu klik di bawah Settings > System > Privacy, tanpa rebuild diperlukan. Pemrosesan file selalu tetap di dalam kontainer Anda.
|
||||
|
||||
Kontainer berjalan sebagai pengguna non-root khusus (`snapotter`) dengan semua kapabilitas Linux dihapus kecuali set minimum yang diperlukan. Untuk kebijakan pengungkapan kerentanan lengkap dan arsitektur keamanan, lihat [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md) di GitHub.
|
||||
|
||||
## Pengerasan Kontainer {#container-hardening}
|
||||
|
||||
[docker-compose.yml default](https://github.com/snapotter-hq/SnapOtter/blob/main/docker/docker-compose.yml) menyertakan pengerasan keamanan produksi. Berikut rincian setiap opsi dan mengapa itu penting:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest
|
||||
ports:
|
||||
# Bind to localhost only for internet-facing deployments:
|
||||
- "127.0.0.1:1349:1349"
|
||||
volumes:
|
||||
- SnapOtter-data:/data
|
||||
- SnapOtter-workspace:/tmp/workspace
|
||||
environment:
|
||||
- AUTH_ENABLED=true
|
||||
- DEFAULT_PASSWORD=change-me-immediately
|
||||
- RATE_LIMIT_PER_MIN=1000
|
||||
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
# --- Resource limits ---
|
||||
mem_limit: 6g # Prevents runaway memory from crashing the host
|
||||
memswap_limit: 6g # No swap - fail fast instead of degrading the host
|
||||
cpus: 4 # Cap CPU usage to 4 cores
|
||||
pids_limit: 512 # Prevents fork bombs
|
||||
|
||||
# --- Capability restrictions ---
|
||||
cap_drop:
|
||||
- ALL # Drop ALL Linux capabilities first
|
||||
cap_add:
|
||||
- CHOWN # Needed for volume permission setup
|
||||
- SETUID # Needed for gosu privilege drop (root -> snapotter)
|
||||
- SETGID # Needed for gosu privilege drop
|
||||
- DAC_OVERRIDE # Needed for volume permission setup
|
||||
- FOWNER # Needed for volume permission setup
|
||||
|
||||
# --- Logging ---
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "50m" # Rotate logs at 50 MB
|
||||
max-file: "5" # Keep 5 rotated log files
|
||||
|
||||
# --- Health check ---
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "--max-time", "5", "http://localhost:1349/api/v1/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 60s
|
||||
retries: 3
|
||||
|
||||
shm_size: "2gb" # Required for Python ML shared memory
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_USER: snapotter
|
||||
POSTGRES_PASSWORD: snapotter
|
||||
POSTGRES_DB: snapotter
|
||||
volumes:
|
||||
- SnapOtter-pgdata:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U snapotter"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 15s
|
||||
|
||||
redis:
|
||||
image: redis:8-alpine
|
||||
command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- SnapOtter-redisdata:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
SnapOtter-data:
|
||||
SnapOtter-workspace:
|
||||
SnapOtter-pgdata:
|
||||
SnapOtter-redisdata:
|
||||
```
|
||||
|
||||
### Mengapa `no-new-privileges` Tidak Diatur {#why-no-new-privileges-is-not-set}
|
||||
|
||||
`security_opt: [no-new-privileges:true]` sengaja dihilangkan. Entrypoint mulai sebagai root untuk memperbaiki kepemilikan volume, lalu turun ke pengguna `snapotter` melalui [gosu](https://github.com/tianon/gosu), yang membutuhkan setuid. Setelah penurunan hak istimewa selesai, proses berjalan sebagai `snapotter` dengan semua kapabilitas kecuali lima yang tercantum di atas dihapus.
|
||||
|
||||
Jika Anda menggunakan Kubernetes atau flag `--user` Docker untuk berjalan sebagai non-root secara langsung (melewati gosu), `no-new-privileges` aman untuk diaktifkan.
|
||||
|
||||
### Mengapa `read_only` Tidak Diatur {#why-read-only-is-not-set}
|
||||
|
||||
`read_only: true` tidak diatur karena remapping PUID/PGID menulis ke `/etc/passwd` dan `/etc/group` saat startup. Jika Anda menggunakan flag `--user` Docker atau `runAsUser` Kubernetes alih-alih PUID/PGID, Anda dapat mengaktifkan filesystem root read-only dengan aman.
|
||||
|
||||
## Isolasi Jaringan {#network-isolation}
|
||||
|
||||
Selama operasi normal, kontainer membuat **nol koneksi jaringan keluar**. Semua pemrosesan file terjadi secara lokal menggunakan pustaka yang disertakan.
|
||||
|
||||
```
|
||||
Browser --> Reverse Proxy (TLS) --> SnapOtter container --> (nothing)
|
||||
```
|
||||
|
||||
Satu-satunya pengecualian adalah **unduhan model AI**: ketika pengguna memasang bundle fitur AI melalui UI, kontainer mengunduh arsip bundle yang telah dibangun sebelumnya dari Hugging Face, ditambah beberapa file model individual dari GitHub Releases, Google Storage, dan PyPI. Unduhan ini terjadi sekali per bundle dan disimpan di volume `/data`.
|
||||
|
||||
**Rekomendasi firewall:**
|
||||
|
||||
| Skenario | Aturan keluar |
|
||||
|---|---|
|
||||
| Air-gapped (tanpa AI) | Blokir semua lalu lintas keluar dari kontainer |
|
||||
| Bundle AI diperlukan | Izinkan HTTPS ke `huggingface.co`, `*.xethub.hf.co`, `cdn-lfs.huggingface.co`, `github.com`, `objects.githubusercontent.com`, `storage.googleapis.com`, `pypi.org`, `files.pythonhosted.org` selama instalasi, lalu blokir |
|
||||
| Setelah instalasi AI | Blokir semua lalu lintas keluar, model di-cache secara lokal |
|
||||
|
||||
Arsip bundle disajikan dari penyimpanan Xet Hugging Face, yang mentransfer melalui endpoint `*.xethub.hf.co` secara paralel dan itulah yang membuat unduhan bundle multi-GB cepat. Jika firewall Anda mengizinkan `huggingface.co` tetapi memblokir `*.xethub.hf.co`, instalasi tetap berhasil tetapi kembali ke unduhan single-stream yang lebih lambat, jadi masukkan host Xet ke allowlist agar tetap di jalur cepat. Instalasi sepenuhnya offline dapat melewati semua ini dan menggunakan [Impor Bundle Offline](/id/guide/deployment) sebagai gantinya.
|
||||
|
||||
Untuk konfigurasi reverse proxy (Nginx, Traefik, Caddy, Cloudflare Tunnels), lihat [panduan Deployment](/id/guide/deployment#reverse-proxy).
|
||||
|
||||
## Docker Secrets {#docker-secrets}
|
||||
|
||||
Untuk deployment produksi, hindari meneruskan secret sebagai variabel lingkungan teks biasa. Entrypoint mendukung konvensi `_FILE` Docker: mount sebuah secret sebagai file dan atur variabel `_FILE` yang sesuai ke path-nya.
|
||||
|
||||
**Secret yang didukung:**
|
||||
|
||||
| Variabel | Setara `_FILE` |
|
||||
|---|---|
|
||||
| `DEFAULT_PASSWORD` | `DEFAULT_PASSWORD_FILE` |
|
||||
| `COOKIE_SECRET` | `COOKIE_SECRET_FILE` |
|
||||
| `OIDC_CLIENT_SECRET` | `OIDC_CLIENT_SECRET_FILE` |
|
||||
| `S3_ACCESS_KEY_ID` | `S3_ACCESS_KEY_ID_FILE` |
|
||||
| `S3_SECRET_ACCESS_KEY` | `S3_SECRET_ACCESS_KEY_FILE` |
|
||||
| `SNAPOTTER_LICENSE_KEY` | `SNAPOTTER_LICENSE_KEY_FILE` |
|
||||
|
||||
**Contoh dengan secret Docker Compose:**
|
||||
|
||||
```yaml
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest
|
||||
environment:
|
||||
- AUTH_ENABLED=true
|
||||
- DEFAULT_USERNAME=admin
|
||||
- DEFAULT_PASSWORD_FILE=/run/secrets/snapotter_password
|
||||
- COOKIE_SECRET_FILE=/run/secrets/cookie_secret
|
||||
secrets:
|
||||
- snapotter_password
|
||||
- cookie_secret
|
||||
|
||||
secrets:
|
||||
snapotter_password:
|
||||
file: ./secrets/snapotter_password.txt
|
||||
cookie_secret:
|
||||
file: ./secrets/cookie_secret.txt
|
||||
```
|
||||
|
||||
::: tip
|
||||
Secret Docker Compose (tanpa Swarm) membutuhkan Compose v2.23 atau lebih baru.
|
||||
:::
|
||||
|
||||
## Deployment Kubernetes {#kubernetes-deployment}
|
||||
|
||||
Entrypoint mendeteksi ketika kontainer sudah berjalan sebagai non-root (misalnya, melalui `runAsUser` Kubernetes) dan melewati penurunan hak istimewa gosu secara otomatis. Dalam kasus itu ia tidak dapat chown volume yang di-mount sendiri, jadi ia memverifikasi bahwa volume dapat ditulis dan keluar lebih awal dengan panduan yang dapat ditindaklanjuti jika tidak, lihat [Izin penyimpanan](/id/guide/deployment#storage-permissions) untuk penyiapan `fsGroup` dan UID asing (TrueNAS, OpenShift).
|
||||
|
||||
**SecurityContext Pod yang direkomendasikan:**
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: snapotter
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: snapotter
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: snapotter
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
fsGroup: 999
|
||||
containers:
|
||||
- name: snapotter
|
||||
image: snapotter/snapotter:latest
|
||||
ports:
|
||||
- containerPort: 1349
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
resources:
|
||||
requests:
|
||||
cpu: "1"
|
||||
memory: 2Gi
|
||||
limits:
|
||||
cpu: "4"
|
||||
memory: 6Gi
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/health
|
||||
port: 1349
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/health
|
||||
port: 1349
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: workspace
|
||||
mountPath: /tmp/workspace
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: snapotter-data
|
||||
- name: workspace
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 2Gi
|
||||
```
|
||||
|
||||
Karena `runAsUser: 999` diatur pada tingkat pod, entrypoint melewati gosu sepenuhnya. Ini memungkinkan kapabilitas `allowPrivilegeEscalation: false` dan `drop: [ALL]` tanpa konflik.
|
||||
|
||||
Untuk penentuan ukuran sumber daya, lihat [Persyaratan Perangkat Keras](/id/guide/deployment#hardware-requirements).
|
||||
|
||||
## Pencadangan dan Pemulihan {#backup-and-recovery}
|
||||
|
||||
State persisten dibagi di dua volume:
|
||||
|
||||
| Volume | Isi | Kritis? |
|
||||
|---|---|---|
|
||||
| `SnapOtter-pgdata` | Basis data PostgreSQL (pengguna, pengaturan, pipeline, job, audit log) | Ya |
|
||||
| `/data` (volume app) | File yang diunggah pengguna, model AI, venv Python | Sebagian (lihat di bawah) |
|
||||
|
||||
Di dalam volume `/data`:
|
||||
|
||||
| Path | Isi | Kritis? |
|
||||
|---|---|---|
|
||||
| `/data/uploads/`, `/data/outputs/` | File pengguna dan hasil pemrosesan | Ya |
|
||||
| `/data/ai/` | File model AI yang diunduh | Tidak (dapat diunduh ulang) |
|
||||
| `/data/venv/` | Lingkungan virtual Python | Tidak (dibangun ulang saat start) |
|
||||
|
||||
### Pencadangan basis data {#database-backup}
|
||||
|
||||
Gunakan `pg_dump` untuk mencadangkan basis data selama stack berjalan:
|
||||
|
||||
```bash
|
||||
# Dump the database
|
||||
docker exec SnapOtter-postgres pg_dump -U snapotter snapotter > backup.sql
|
||||
|
||||
# Restore into a fresh database
|
||||
cat backup.sql | docker exec -i SnapOtter-postgres psql -U snapotter snapotter
|
||||
```
|
||||
|
||||
Atau, hentikan stack dan buat snapshot volume `SnapOtter-pgdata`:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker run --rm -v SnapOtter-pgdata:/data -v $(pwd)/backup:/backup \
|
||||
alpine tar czf /backup/snapotter-pgdata.tar.gz -C /data .
|
||||
```
|
||||
|
||||
### Pencadangan file pengguna {#user-files-backup}
|
||||
|
||||
```bash
|
||||
# Snapshot the app data volume (excluding re-downloadable AI models)
|
||||
docker run --rm -v SnapOtter-data:/data -v $(pwd)/backup:/backup \
|
||||
alpine tar czf /backup/snapotter-files.tar.gz \
|
||||
--exclude='ai' --exclude='venv' -C /data .
|
||||
```
|
||||
|
||||
Model AI berjumlah hingga sekitar 24 GB di semua bundle. Karena dapat diunduh ulang, kecualikan `/data/ai/` dan `/data/venv/` dari pencadangan untuk menghemat ruang. Hanya basis data dan file pengguna yang kritis.
|
||||
|
||||
## Artefak Kepatuhan {#compliance-artifacts}
|
||||
|
||||
Setiap rilis SnapOtter menyertakan artefak keamanan berikut:
|
||||
|
||||
| Artefak | Format | Di mana menemukannya |
|
||||
|---|---|---|
|
||||
| SBOM (CycloneDX) | JSON | Aset [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases): `snapotter-v{version}-sbom.cdx.json` |
|
||||
| SBOM (SPDX) | JSON | Aset [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases): `snapotter-v{version}-sbom.spdx.json` |
|
||||
| Pemindaian kerentanan | Trivy JSON | Aset [GitHub Release](https://github.com/snapotter-hq/SnapOtter/releases): `snapotter-v{version}-trivy.json` |
|
||||
| Pemindaian kerentanan | SARIF | Tab [GitHub Security](https://github.com/snapotter-hq/SnapOtter/security) |
|
||||
| Analisis statis | CodeQL (JS/TS + Python) | Tab [GitHub Security](https://github.com/snapotter-hq/SnapOtter/security), berjalan mingguan + per PR |
|
||||
| Tinjauan dependensi | GitHub native | Pemeriksaan per-PR, gagal pada penambahan severity tinggi |
|
||||
| Audit dependensi Python | pip-audit | Log run CI pada setiap push |
|
||||
| Kebijakan keamanan | Markdown | [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md) di repositori |
|
||||
| Pembaruan dependensi | Dependabot | PR mingguan otomatis untuk npm, pip, Docker, Actions |
|
||||
|
||||
**Menjalankan pemindaian Anda sendiri:**
|
||||
|
||||
Unduh SBOM dari rilis dan pindai dengan perkakas pilihan Anda:
|
||||
|
||||
```bash
|
||||
# Scan with Grype using the CycloneDX SBOM
|
||||
grype sbom:snapotter-v1.17.2-sbom.cdx.json
|
||||
|
||||
# Scan with Trivy using the SPDX SBOM
|
||||
trivy sbom snapotter-v1.17.2-sbom.spdx.json
|
||||
|
||||
# Scan the Docker image directly
|
||||
trivy image snapotter/snapotter:1.17.2
|
||||
```
|
||||
|
||||
::: info
|
||||
SBOM dan pemindaian kerentanan mencerminkan image persis yang dipublikasikan untuk rilis itu. Bundle model AI yang dipasang setelah deployment tidak disertakan dalam SBOM karena diunduh saat runtime.
|
||||
:::
|
||||
@@ -0,0 +1,239 @@
|
||||
---
|
||||
description: "Format file yang didukung di semua modalitas - 55+ format masukan gambar, video, audio, PDF, dan format file."
|
||||
i18n_source_hash: e53ecf65be25
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 4bbbac7f641d
|
||||
---
|
||||
|
||||
# Format yang Didukung {#supported-formats}
|
||||
|
||||
SnapOtter memproses file di lima modalitas: image, video, audio, PDF, dan files. Halaman ini mencantumkan semua format yang didukung.
|
||||
|
||||
## Format Gambar {#image-formats}
|
||||
|
||||
SnapOtter mendukung 55+ format gambar untuk masukan dan 13 format untuk keluaran.
|
||||
|
||||
## Format Masukan {#input-formats}
|
||||
|
||||
### Standar Web (9) {#web-standards-9}
|
||||
|
||||
| Format | Ekstensi | Decoder | Catatan |
|
||||
|--------|-----------|---------|-------|
|
||||
| JPEG | .jpg, .jpeg | Sharp (native) | |
|
||||
| PNG | .png | Sharp (native) | Frame pertama APNG diekstrak |
|
||||
| WebP | .webp | Sharp (native) | |
|
||||
| GIF | .gif | Sharp (native) | Animasi didukung |
|
||||
| AVIF | .avif | Sharp (native) | |
|
||||
| SVG | .svg | Sharp (librsvg) | Disanitasi untuk XXE/SSRF |
|
||||
| SVGZ | .svgz | gunzip + Sharp | Proteksi gzip bomb |
|
||||
| APNG | .apng | Sharp (native) | Hanya frame pertama |
|
||||
| JPEG XL | .jxl | djxl / ImageMagick | Fallback dua tingkat |
|
||||
|
||||
### Profesional (7) {#professional-7}
|
||||
|
||||
| Format | Ekstensi | Decoder | Catatan |
|
||||
|--------|-----------|---------|-------|
|
||||
| TIFF | .tiff, .tif | Sharp (native) | Multi-halaman didukung |
|
||||
| PSD | .psd | ImageMagick | Komposit yang diratakan |
|
||||
| EPS | .eps, .epsf | ImageMagick + Ghostscript | Rasterisasi 300dpi, diperkuat keamanannya |
|
||||
| OpenEXR | .exr | ImageMagick | Konversi linear-ke-sRGB |
|
||||
| Radiance HDR | .hdr | ImageMagick | Konversi linear-ke-sRGB |
|
||||
| DPX | .dpx | ImageMagick | Konversi log-ke-sRGB |
|
||||
| Cineon | .cin | ImageMagick | Format Film/VFX |
|
||||
|
||||
### Camera RAW (23) {#camera-raw-23}
|
||||
|
||||
| Format | Ekstensi | Merek Kamera | Decoder |
|
||||
|--------|-----------|-------------|---------|
|
||||
| DNG | .dng | Adobe (universal) | exiftool / ImageMagick + LibRaw |
|
||||
| CR2 | .cr2 | Canon (sebelum 2018) | exiftool / ImageMagick + LibRaw |
|
||||
| CR3 | .cr3 | Canon (2018+) | exiftool / ImageMagick + LibRaw |
|
||||
| NEF | .nef | Nikon | exiftool / ImageMagick + LibRaw |
|
||||
| NRW | .nrw | Nikon (Coolpix) | exiftool / ImageMagick + LibRaw |
|
||||
| ARW | .arw | Sony | exiftool / ImageMagick + LibRaw |
|
||||
| ORF | .orf | Olympus | exiftool / ImageMagick + LibRaw |
|
||||
| RW2 | .rw2 | Panasonic | exiftool / ImageMagick + LibRaw |
|
||||
| RAF | .raf | Fujifilm | exiftool / ImageMagick + LibRaw |
|
||||
| PEF | .pef | Pentax/Ricoh | exiftool / ImageMagick + LibRaw |
|
||||
| 3FR | .3fr | Hasselblad | exiftool / ImageMagick + LibRaw |
|
||||
| IIQ | .iiq | Phase One | exiftool / ImageMagick + LibRaw |
|
||||
| SRW | .srw | Samsung | exiftool / ImageMagick + LibRaw |
|
||||
| X3F | .x3f | Sigma | exiftool / ImageMagick + LibRaw |
|
||||
| RWL | .rwl | Leica | exiftool / ImageMagick + LibRaw |
|
||||
| GPR | .gpr | GoPro | exiftool / ImageMagick + LibRaw |
|
||||
| FFF | .fff | Hasselblad (lawas) | exiftool / ImageMagick + LibRaw |
|
||||
| MRW | .mrw | Minolta | exiftool / ImageMagick + LibRaw |
|
||||
| MEF | .mef | Mamiya | exiftool / ImageMagick + LibRaw |
|
||||
| KDC | .kdc | Kodak | exiftool / ImageMagick + LibRaw |
|
||||
| DCR | .dcr | Kodak | exiftool / ImageMagick + LibRaw |
|
||||
| ERF | .erf | Epson | exiftool / ImageMagick + LibRaw |
|
||||
| PTX | .ptx | Pentax (compact) | exiftool / ImageMagick + LibRaw |
|
||||
|
||||
### Format Modern (3) {#modern-formats-3}
|
||||
|
||||
| Format | Ekstensi | Decoder | Catatan |
|
||||
|--------|-----------|---------|-------|
|
||||
| JPEG 2000 | .jp2, .j2k, .j2c, .jpc, .jpf, .jpx | opj_decompress / ImageMagick | Sinema digital, pencitraan medis |
|
||||
| QOI | .qoi | Codec TypeScript inline | Pengembangan game, sistem tertanam |
|
||||
| HEIC/HEIF | .heic, .heif | heif-convert / heif-dec | Foto iPhone |
|
||||
|
||||
### Lawas/Sistem (4) {#legacy-system-4}
|
||||
|
||||
| Format | Ekstensi | Decoder | Catatan |
|
||||
|--------|-----------|---------|-------|
|
||||
| BMP | .bmp | ImageMagick | |
|
||||
| ICO | .ico | ImageMagick | Layer terbesar diekstrak |
|
||||
| CUR | .cur | ImageMagick | Kursor Windows (varian ICO) |
|
||||
| TGA | .tga | ImageMagick | Deteksi hanya lewat ekstensi |
|
||||
|
||||
### Ilmiah dan Gaming (2) {#scientific-and-gaming-2}
|
||||
|
||||
| Format | Ekstensi | Decoder | Catatan |
|
||||
|--------|-----------|---------|-------|
|
||||
| FITS | .fits, .fit, .fts | ImageMagick | Astronomi (standar NASA) |
|
||||
| DDS | .dds | ImageMagick | Tekstur game (DirectX) |
|
||||
|
||||
### Interchange (6) {#interchange-6}
|
||||
|
||||
| Format | Ekstensi | Decoder | Catatan |
|
||||
|--------|-----------|---------|-------|
|
||||
| PPM | .ppm | Sharp (native) | Pixmap berwarna |
|
||||
| PGM | .pgm | Sharp (native) | Grayscale |
|
||||
| PBM | .pbm | Sharp (native) | Bitmap 1-bit |
|
||||
| PNM | .pnm | Sharp (native) | Format payung |
|
||||
| PAM | .pam | Sharp (native) | Peta arbitrer |
|
||||
| PFM | .pfm | Sharp (native) | Peta float |
|
||||
|
||||
## Format Keluaran (13) {#output-formats-13}
|
||||
|
||||
| Format | Encoder | Kontrol Kualitas | Tersedia Di |
|
||||
|--------|---------|----------------|-------------|
|
||||
| JPEG | Sharp native | 1-100 | Semua alat |
|
||||
| PNG | Sharp native | Kompresi 0-9 | Semua alat |
|
||||
| WebP | Sharp native | 1-100 | Semua alat |
|
||||
| AVIF | Sharp native | 1-100 | Semua alat |
|
||||
| TIFF | Sharp native | 1-100 | Alat konversi penuh |
|
||||
| GIF | Sharp native | 1-100 | Alat konversi penuh |
|
||||
| JXL | Sharp native | 1-100 | Semua alat |
|
||||
| HEIC | heif-enc CLI | 1-100 | Alat konversi penuh |
|
||||
| HEIF | heif-enc CLI | 1-100 | Alat konversi penuh |
|
||||
| BMP | ImageMagick CLI | Lossless | Alat convert |
|
||||
| ICO | ImageMagick CLI | Lossless | Alat convert |
|
||||
| JP2 | opj_compress CLI | Rasio kompresi | Alat convert |
|
||||
| QOI | Codec inline | Lossless | Alat convert |
|
||||
|
||||
## Format Video {#video-formats}
|
||||
|
||||
Penguraian dan pengodean video ditangani oleh FFmpeg (build statis), sehingga setiap container dan codec umum didukung pada masukan.
|
||||
|
||||
### Container Masukan (15) {#input-containers-15}
|
||||
|
||||
| Format | Ekstensi | Codec umum | Catatan |
|
||||
|--------|-----------|----------------|-------|
|
||||
| MP4 | .mp4 | H.264, H.265, AV1 | Container yang paling banyak digunakan |
|
||||
| QuickTime | .mov | H.264, ProRes | Perekaman/penyuntingan Apple |
|
||||
| WebM | .webm | VP8, VP9, AV1 | Format web bebas royalti |
|
||||
| Matroska | .mkv | Apa pun | Container terbuka yang fleksibel |
|
||||
| AVI | .avi | Beragam | Container lawas Microsoft |
|
||||
| M4V | .m4v | H.264 | Varian MP4 Apple |
|
||||
| AVCHD | .mts | H.264 | Rekaman camcorder |
|
||||
| BDAV | .m2ts | H.264 | Transport stream Blu-ray / AVCHD |
|
||||
| 3GP | .3gp | H.264, MPEG-4 | Perekaman seluler |
|
||||
| Flash Video | .flv | H.264, VP6 | Streaming lawas |
|
||||
| Windows Media | .wmv | VC-1, WMV | Windows Media |
|
||||
| MPEG | .mpg, .mpeg | MPEG-1, MPEG-2 | Video era DVD |
|
||||
| MPEG-TS | .ts | MPEG-2, H.264 | Transport stream siaran |
|
||||
| Ogg | .ogv | Theora | Video Ogg terbuka |
|
||||
|
||||
### Format Keluaran {#output-formats}
|
||||
|
||||
| Format | Ekstensi | Codec video | Dihasilkan oleh |
|
||||
|--------|-----------|-------------|-------------|
|
||||
| MP4 | .mp4 | H.264 | Convert, compress, dan sebagian besar alat video |
|
||||
| QuickTime | .mov | H.264 | Convert Video |
|
||||
| WebM | .webm | VP9 | Convert Video |
|
||||
| GIF | .gif | - | Video to GIF |
|
||||
| WebP | .webp | - | Video to WebP (animasi) |
|
||||
|
||||
### Subtitle {#subtitles}
|
||||
|
||||
| Format | Ekstensi | Operasi |
|
||||
|--------|-----------|-----------|
|
||||
| SubRip | .srt | Embed, burn-in, ekstrak, buat otomatis |
|
||||
| WebVTT | .vtt | Embed, burn-in, ekstrak, buat otomatis |
|
||||
| ASS / SSA | .ass | Embed, burn-in (mendukung styling) |
|
||||
|
||||
## Format Audio {#audio-formats}
|
||||
|
||||
Audio juga diproses oleh FFmpeg.
|
||||
|
||||
### Format Masukan (11) {#input-formats-11}
|
||||
|
||||
| Format | Ekstensi | Kompresi | Catatan |
|
||||
|--------|-----------|-------------|-------|
|
||||
| MP3 | .mp3 | Lossy | Kompatibilitas universal |
|
||||
| WAV | .wav | Tanpa kompresi (PCM) | Studio / penyuntingan |
|
||||
| FLAC | .flac | Lossless | Codec lossless terbuka |
|
||||
| AAC | .aac | Lossy | Aliran AAC mentah |
|
||||
| M4A | .m4a | Lossy (AAC) / Lossless (ALAC) | Audio MPEG-4 |
|
||||
| Ogg Vorbis | .ogg | Lossy | Format terbuka |
|
||||
| Opus | .opus | Lossy | Modern, latensi rendah |
|
||||
| WMA | .wma | Lossy | Windows Media Audio |
|
||||
| AIFF | .aiff | Tanpa kompresi (PCM) | Tanpa kompresi Apple |
|
||||
| AMR | .amr | Lossy | Suara / seluler |
|
||||
| AC-3 | .ac3 | Lossy | Dolby Digital |
|
||||
|
||||
### Format Keluaran {#output-formats-1}
|
||||
|
||||
| Format | Ekstensi | Codec | Dihasilkan oleh |
|
||||
|--------|-----------|-------|-------------|
|
||||
| MP3 | .mp3 | LAME | Convert Audio, Extract Audio |
|
||||
| WAV | .wav | PCM | Convert Audio, Extract Audio |
|
||||
| FLAC | .flac | FLAC (lossless) | Convert Audio |
|
||||
| Ogg | .ogg | Vorbis | Convert Audio |
|
||||
| M4A | .m4a | AAC | Convert Audio, Extract Audio |
|
||||
|
||||
## Format Dokumen {#document-formats}
|
||||
|
||||
Pemrosesan dokumen menggunakan qpdf, LibreOffice, Ghostscript, Pandoc, dan WeasyPrint.
|
||||
|
||||
### Format Masukan (15) {#input-formats-15}
|
||||
|
||||
| Format | Ekstensi | Engine | Catatan |
|
||||
|--------|-----------|--------|-------|
|
||||
| PDF | .pdf | qpdf, Ghostscript, pdfcpu | Format dokumen inti |
|
||||
| Word | .docx, .doc | LibreOffice | Microsoft Word |
|
||||
| Excel | .xlsx, .xls | LibreOffice | Microsoft Excel |
|
||||
| PowerPoint | .pptx, .ppt | LibreOffice | Microsoft PowerPoint |
|
||||
| OpenDocument | .odt, .ods, .odp | LibreOffice | Teks, lembar, presentasi |
|
||||
| Rich Text | .rtf | LibreOffice | Rich text lintas aplikasi |
|
||||
| Plain Text | .txt | LibreOffice, Pandoc | Teks UTF-8 |
|
||||
| Markdown | .md | Pandoc | CommonMark / GFM |
|
||||
| HTML | .html | WeasyPrint | Dirender ke PDF |
|
||||
| EPUB | .epub | Pandoc, LibreOffice | Format e-book |
|
||||
|
||||
### Format Keluaran {#output-formats-2}
|
||||
|
||||
| Format | Ekstensi | Dihasilkan oleh |
|
||||
|--------|-----------|-------------|
|
||||
| PDF | .pdf | Word/Excel/PowerPoint ke PDF, Markdown ke PDF, HTML ke PDF |
|
||||
| PDF/A | .pdf | PDF/A Convert (arsip) |
|
||||
| Word | .docx, .odt, .rtf, .txt | Convert Document, PDF ke Word, Markdown ke Word |
|
||||
| Presentasi | .pptx, .odp | Convert Presentation |
|
||||
| Spreadsheet | .xlsx, .ods, .csv | Convert Spreadsheet |
|
||||
| HTML | .html | Markdown ke HTML |
|
||||
| EPUB | .epub | Convert ke EPUB |
|
||||
| Gambar | .png, .jpg | PDF ke Image |
|
||||
|
||||
## Format File {#file-formats}
|
||||
|
||||
Alat data dan arsip mengonversi antar format terstruktur dan mengemas file.
|
||||
|
||||
| Format | Ekstensi | Konversi |
|
||||
|--------|-----------|-------------|
|
||||
| CSV | .csv | Ke/dari JSON dan Excel; split dan merge; dari XML |
|
||||
| JSON | .json | Ke/dari CSV, XML, dan YAML |
|
||||
| XML | .xml | Ke/dari JSON; ke CSV |
|
||||
| YAML | .yaml, .yml | Ke/dari JSON |
|
||||
| Excel | .xlsx | Ke/dari CSV |
|
||||
| ZIP | .zip | Buat arsip, ekstrak isi |
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
description: "Data penggunaan anonim apa yang dikumpulkan SnapOtter, kapan dikirim, dan cara mematikan analitik produk untuk seluruh instance."
|
||||
i18n_source_hash: 5d72dedaeb23
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: b753bc59abac
|
||||
---
|
||||
|
||||
# Apa yang dikumpulkan SnapOtter {#what-snapotter-collects}
|
||||
|
||||
Analitik Produk Anonim aktif secara default dan diatur untuk seluruh instance oleh administrator. Matikan di Settings > System > Privacy.
|
||||
|
||||
## Peristiwa yang kami kirim (ketika diaktifkan) {#events-we-send-when-enabled}
|
||||
|
||||
- tool_used: id alat, status, durasi, kategori, apakah ini alat AI, kode error saat gagal.
|
||||
- pipeline_executed: jumlah langkah, id alat, flag batch, jumlah file, durasi, status.
|
||||
- ai_bundle_action: id bundle, aksi, durasi.
|
||||
- Penggunaan frontend: halaman alat mana yang dibuka, file ditambahkan (hanya jumlah), alat dimulai, unduhan, penyimpanan, pencarian (hanya jumlah hasil), batch diproses.
|
||||
- Laporan crash: tipe error dan source stack dengan hanya basename file.
|
||||
|
||||
## Apa yang tidak pernah kami kumpulkan {#what-we-never-collect}
|
||||
|
||||
- Nama atau path file
|
||||
- Isi file
|
||||
- Teks keluaran OCR
|
||||
- Metadata gambar (EXIF)
|
||||
- Teks dokumen yang diekstrak
|
||||
- Alamat IP atau identitas akun Anda
|
||||
|
||||
## Cara mematikannya {#turning-it-off}
|
||||
|
||||
Admin: Settings > System > Privacy, matikan "Anonymous Product Analytics". Pengiriman langsung berhenti, untuk seluruh instance. Untuk membangun image yang tidak akan pernah mengirim, atur build arg `SNAPOTTER_ANALYTICS=off`.
|
||||
@@ -0,0 +1,234 @@
|
||||
---
|
||||
description: "21 bahasa yang didukung dan cara membuat atau meningkatkan terjemahan untuk SnapOtter menggunakan sistem i18n yang diberlakukan oleh TypeScript."
|
||||
i18n_source_hash: 55837d9fdaef
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 00579c8e1791
|
||||
---
|
||||
|
||||
# Panduan terjemahan {#translation-guide}
|
||||
|
||||
SnapOtter hadir dengan 21 bahasa secara bawaan. Sistem i18n menggunakan runtime khusus yang ringan dengan kelengkapan locale yang diberlakukan oleh TypeScript dan pemisahan kode dinamis.
|
||||
|
||||
## Bahasa yang didukung {#supported-languages}
|
||||
|
||||
| Code | Language | Native Name | Direction |
|
||||
|------|----------|-------------|-----------|
|
||||
| `en` | Inggris | English | LTR |
|
||||
| `zh-CN` | Tionghoa (Sederhana) | 简体中文 | LTR |
|
||||
| `zh-TW` | Tionghoa (Tradisional) | 繁體中文 | LTR |
|
||||
| `ja` | Jepang | 日本語 | LTR |
|
||||
| `ko` | Korea | 한국어 | LTR |
|
||||
| `es` | Spanyol | Español | LTR |
|
||||
| `fr` | Prancis | Français | LTR |
|
||||
| `it` | Italia | Italiano | LTR |
|
||||
| `pt-BR` | Portugis (Brasil) | Português (Brasil) | LTR |
|
||||
| `de` | Jerman | Deutsch | LTR |
|
||||
| `nl` | Belanda | Nederlands | LTR |
|
||||
| `sv` | Swedia | Svenska | LTR |
|
||||
| `ru` | Rusia | Русский | LTR |
|
||||
| `pl` | Polandia | Polski | LTR |
|
||||
| `uk` | Ukraina | Українська | LTR |
|
||||
| `ar` | Arab | العربية | RTL |
|
||||
| `tr` | Turki | Türkçe | LTR |
|
||||
| `hi` | Hindi | हिन्दी | LTR |
|
||||
| `vi` | Vietnam | Tiếng Việt | LTR |
|
||||
| `id` | Indonesia | Bahasa Indonesia | LTR |
|
||||
| `th` | Thai | ไทย | LTR |
|
||||
|
||||
## Cara kerja deteksi bahasa {#how-language-detection-works}
|
||||
|
||||
SnapOtter menggunakan urutan resolusi tiga tingkat:
|
||||
|
||||
1. **Preferensi pengguna** - disimpan di `localStorage("snapotter-locale")` dan disinkronkan ke pengaturan pengguna saat terautentikasi
|
||||
2. **Deteksi otomatis browser** - menelusuri array `navigator.languages` dengan pencocokan prefiks BCP 47
|
||||
3. **Bawaan instans** - variabel env `DEFAULT_LOCALE` milik admin (diambil dari `GET /api/v1/config/locale`)
|
||||
4. **Cadangan bahasa Inggris** - selalu tersedia
|
||||
|
||||
Pengguna dapat mengubah bahasa dari:
|
||||
- **Pemilih Globe di footer** (desktop, selalu terlihat)
|
||||
- Pemilih bahasa di **halaman login** (sebelum autentikasi)
|
||||
- Bagian **Settings > General** (preferensi per pengguna)
|
||||
- Dropdown bahasa di **sidebar seluler**
|
||||
- Bagian **Settings > System** yang menetapkan bawaan seluruh instans (khusus admin)
|
||||
|
||||
## Cara kerja terjemahan {#how-translations-work}
|
||||
|
||||
Semua string UI berada di `packages/shared/src/i18n/`. File referensinya adalah `en.ts`, yang mengekspor objek bertipe berisi setiap string yang digunakan aplikasi (~1500 kunci). Bahasa lain adalah file terpisah (mis. `de.ts`, `fr.ts`) yang mengekspor bentuk yang sama.
|
||||
|
||||
Tipe `TranslationKeys` menggunakan `DeepStringRecord` untuk menerima nilai string apa pun sambil memberlakukan struktur kunci. TypeScript menangkap kunci yang hilang di file terjemahan mana pun pada waktu kompilasi.
|
||||
|
||||
Hanya locale aktif yang dimuat saat runtime melalui `import()` dinamis, menjaga bundel utama tetap kecil.
|
||||
|
||||
## Menggunakan terjemahan dalam komponen {#using-translations-in-components}
|
||||
|
||||
```tsx
|
||||
import { useTranslation } from "@/contexts/i18n-context";
|
||||
import { format, plural } from "@/lib/format";
|
||||
|
||||
function MyComponent() {
|
||||
const { t, locale, setLocale } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{t.common.settings}</h1>
|
||||
<p>{format(t.settings.people.deleteConfirm, { username: "admin" })}</p>
|
||||
<p>{plural(count, t.automate.fileCount, t.automate.fileCountPlural)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Menyumbangkan terjemahan {#contributing-a-translation}
|
||||
|
||||
Kami menyambut PR terjemahan secara langsung. Anda dapat meningkatkan locale yang sudah ada atau menambahkan yang baru.
|
||||
|
||||
Untuk melaporkan kesalahan terjemahan tanpa mengirimkan kode, buka [GitHub Issue](https://github.com/snapotter-hq/SnapOtter/issues) dengan bahasa, string yang salah, dan perbaikan yang disarankan.
|
||||
|
||||
::: tip
|
||||
PR terjemahan tidak memerlukan persetujuan sebelumnya. Fork repo, lakukan perubahan Anda, dan buka PR. Lihat [Contributing Guide](/id/guide/contributing) untuk proses PR lengkap dan persyaratan CLA.
|
||||
:::
|
||||
|
||||
## Cara membuat atau memperbarui terjemahan {#how-to-create-or-update-a-translation}
|
||||
|
||||
### 1. Fork dan clone {#_1-fork-and-clone}
|
||||
|
||||
```bash
|
||||
git clone https://github.com/<your-username>/snapotter.git
|
||||
cd snapotter
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 2. Salin file referensi (hanya bahasa baru) {#_2-copy-the-reference-file-new-language-only}
|
||||
|
||||
Lewati langkah ini jika Anda meningkatkan terjemahan yang sudah ada.
|
||||
|
||||
```bash
|
||||
cp packages/shared/src/i18n/en.ts packages/shared/src/i18n/XX.ts
|
||||
```
|
||||
|
||||
### 3. Terjemahkan string {#_3-translate-the-strings}
|
||||
|
||||
Buka file baru Anda dan terjemahkan setiap nilai string. Pertahankan struktur objek dan kunci persis sama.
|
||||
|
||||
```ts
|
||||
import type { TranslationKeys } from "./en.js";
|
||||
|
||||
export const xx: TranslationKeys = {
|
||||
common: {
|
||||
upload: "Your translation here",
|
||||
// ... translate all entries
|
||||
},
|
||||
// ... translate all sections
|
||||
} as const;
|
||||
```
|
||||
|
||||
Aturan:
|
||||
- Jangan menerjemahkan kunci objek, hanya nilai string
|
||||
- Pertahankan `as const` di akhir
|
||||
- Impor `TranslationKeys` dari `./en.js` dan beri tipe pada ekspor Anda
|
||||
- Pertahankan placeholder `{variable}` persis apa adanya
|
||||
- Array (`rotatingPhrases`, `progressMessages`) harus memiliki jumlah entri yang sama
|
||||
- Jangan menerjemahkan: SnapOtter, JPEG, PNG, WebP, EXIF, API, dan istilah teknis lainnya
|
||||
|
||||
### 4. Daftarkan locale (hanya bahasa baru) {#_4-register-the-locale-new-language-only}
|
||||
|
||||
Tambahkan locale Anda ke `SUPPORTED_LOCALES` di `packages/shared/src/i18n/index.ts`:
|
||||
|
||||
```ts
|
||||
{ code: "xx", name: "Language Name", nativeName: "Native Name", dir: "ltr" },
|
||||
```
|
||||
|
||||
### 5. Verifikasi {#_5-verify}
|
||||
|
||||
```bash
|
||||
pnpm typecheck # catches missing or mistyped keys
|
||||
pnpm lint # formatting check
|
||||
pnpm dev # manually verify strings appear correctly
|
||||
```
|
||||
|
||||
### 6. Kirim {#_6-submit}
|
||||
|
||||
Buka PR terhadap `main` dengan judul seperti `feat(i18n): add Swedish translation` atau `fix(i18n): correct German typos`. Bot CLA akan meminta Anda menandatangani pada kontribusi pertama Anda.
|
||||
|
||||
## Menambahkan kunci terjemahan baru {#adding-new-translation-keys}
|
||||
|
||||
Saat menambahkan fitur baru yang memerlukan string UI baru:
|
||||
|
||||
1. Tambahkan kunci baru ke `en.ts` terlebih dahulu (file referensi)
|
||||
2. Jalankan `pnpm typecheck` - setiap file locale akan gagal jika kekurangan kunci baru
|
||||
3. Tambahkan kunci baru ke semua file locale (gunakan bahasa Inggris sebagai cadangan sementara)
|
||||
|
||||
## Konfigurasi {#configuration}
|
||||
|
||||
Tetapkan bahasa bawaan instans melalui variabel lingkungan:
|
||||
|
||||
```yaml
|
||||
DEFAULT_LOCALE: "de" # German as the default for all new users
|
||||
```
|
||||
|
||||
## Referensi file {#file-reference}
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `packages/shared/src/i18n/en.ts` | String bahasa Inggris (locale referensi, ~1500 kunci) |
|
||||
| `packages/shared/src/i18n/index.ts` | `SUPPORTED_LOCALES`, `loadTranslations()`, ekspor tipe |
|
||||
| `packages/shared/src/i18n/<locale>.ts` | File terjemahan per bahasa |
|
||||
| `apps/web/src/contexts/i18n-context.tsx` | `I18nProvider`, hook `useTranslation()` |
|
||||
| `apps/web/src/lib/format.ts` | Helper `format()`, `plural()`, `formatFileSize()` |
|
||||
| `apps/api/src/routes/config.ts` | Endpoint publik `GET /api/v1/config/locale` |
|
||||
|
||||
## Menerjemahkan situs web, dokumentasi, dan referensi API {#translating-the-web-surfaces}
|
||||
|
||||
Dukungan 21 bahasa di atas mencakup **aplikasi**. Situs web publik
|
||||
(snapotter.com), situs dokumentasi ini, dan referensi REST API juga
|
||||
diterjemahkan ke seluruh 21 bahasa, oleh pipeline terpisah yang digerbang hash dan menggunakan kembali
|
||||
nama dan deskripsi tool yang sama dari `packages/shared/src/i18n`, sehingga
|
||||
terminologi tetap konsisten di mana-mana.
|
||||
|
||||
### Diterjemahkan mesin secara bawaan {#machine-translated-by-default}
|
||||
|
||||
Setiap halaman non-Inggris di situs web dan dokumentasi **diterjemahkan mesin** pada
|
||||
lintasan pertama (oleh sesi Claude Code, bukan layanan pihak ketiga) dan membawa
|
||||
banner kecil yang dapat ditutup yang menyatakan demikian, dengan tautan kembali ke sini. Itu disengaja:
|
||||
ia mengirimkan seluruh 21 bahasa dengan cepat dan jujur, lalu mengundang komunitas untuk
|
||||
menyempurnakan halaman yang paling penting. Terjemahan mesin menyampaikan maknanya;
|
||||
tinjauan manusia membuatnya terbaca secara alami.
|
||||
|
||||
### Cara pipeline memutuskan apa yang diterjemahkan {#how-the-web-pipeline-decides}
|
||||
|
||||
Setiap unit sumber bahasa Inggris yang dapat diterjemahkan di-hash, dan hash-nya disimpan di samping
|
||||
terjemahannya. Pada setiap eksekusi, pipeline:
|
||||
|
||||
- menerjemahkan unit apa pun yang belum memiliki terjemahan,
|
||||
- melewati unit apa pun yang hash tersimpannya masih cocok dengan sumber bahasa Inggris,
|
||||
- menerjemahkan ulang unit **mesin** ketika sumber bahasa Inggrisnya berubah,
|
||||
- dan menandai unit yang disempurnakan **manusia** sebagai `stale` (perlu ditinjau) ketika sumber
|
||||
bahasa Inggrisnya berubah, alih-alih menimpa pekerjaan Anda.
|
||||
|
||||
### Menyempurnakan terjemahan web melalui PR {#refining-a-web-translation-by-pr}
|
||||
|
||||
Anda meningkatkan terjemahan situs web, dokumentasi, atau referensi API dengan cara yang sama seperti Anda
|
||||
meningkatkan locale aplikasi: dengan menyunting file yang dihasilkan dan membuka PR.
|
||||
|
||||
1. Temukan terjemahan yang dihasilkan untuk bahasa Anda:
|
||||
- string UI situs web: `apps/landing/src/i18n/<locale>.json`
|
||||
- halaman dokumentasi: `apps/docs/<locale>/**.md`
|
||||
- referensi API: `apps/api/src/openapi.<locale>.yaml`
|
||||
2. Sunting teksnya. Pertahankan kode, tautan, `{placeholders}`, dan penanda `⸤I18N…⸥` apa pun
|
||||
persis seperti apa adanya; validator pipeline menolak terjemahan yang menghilangkan
|
||||
atau menyusun ulang keduanya.
|
||||
3. Buka PR. Menyunting sebuah unit membalik provenansinya dari `machine` menjadi `human`, sehingga
|
||||
pipeline akan **tidak pernah menimpanya** pada eksekusi berikutnya. Jika sumber bahasa Inggris
|
||||
berubah setelahnya, unit Anda ditandai `stale` untuk ditinjau alih-alih
|
||||
diganti secara diam-diam.
|
||||
|
||||
Untuk melaporkan kesalahan terjemahan tanpa mengirimkan kode, buka
|
||||
[GitHub Issue](https://github.com/snapotter-hq/SnapOtter/issues) dengan
|
||||
URL halaman, bahasa, teks yang salah, dan perbaikan yang Anda sarankan.
|
||||
|
||||
::: tip
|
||||
Pemelihara menjalankan pipeline terjemahan; Anda tidak memerlukan kunci API untuk
|
||||
berkontribusi. Cukup sunting file yang dihasilkan dan buka PR. Lihat
|
||||
[`scripts/i18n/README.md`](https://github.com/snapotter-hq/SnapOtter/blob/main/scripts/i18n/README.md)
|
||||
untuk cara pipeline berjalan.
|
||||
:::
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
i18n_source_hash: 9a6abf3fc8ae
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: c8e22583e597
|
||||
---
|
||||
# Meningkatkan dari 1.x ke 2.0 {#upgrading-from-1-x-to-2-0}
|
||||
|
||||
SnapOtter 1.x menyimpan semuanya dalam satu file SQLite dan berjalan sebagai satu kontainer. SnapOtter 2.0 menggunakan PostgreSQL dan Redis. Panduan ini memandu pemindahan instalasi 1.x ke 2.0 tanpa kehilangan data.
|
||||
|
||||
Versi singkatnya: gunakan kembali volume `/data` yang sudah ada, dan 2.0 mengimpor database 1.x Anda secara otomatis pada boot pertama. Pengguna, file tersimpan, pengaturan, kunci API, dan pipeline Anda ikut terbawa. Database lama tidak pernah diubah, jadi Anda selalu dapat melakukan rollback.
|
||||
|
||||
::: tip Catatan untuk pengguna 1.x kami
|
||||
Banyak dari Anda telah memercayai SnapOtter sejak hari pertama, dan masukan Anda membentuk rilis ini. 2.0 banyak berubah di balik layar, dan panduan ini ada agar perpindahan tidak merugikan hal-hal yang Anda pedulikan. Akun, file, pengaturan, kunci API, dan pipeline Anda ikut terbawa, dan database lama Anda tidak pernah tersentuh. Terima kasih telah meningkatkan bersama kami.
|
||||
:::
|
||||
|
||||
## Sebelum mulai: cadangkan seluruh volume `/data` {#before-you-start-back-up-the-whole-data-volume}
|
||||
|
||||
Lakukan ini terlebih dahulu, setiap kali. Cadangkan **seluruh** volume `/data`, bukan hanya file `snapotter.db`.
|
||||
|
||||
Beginilah alasannya penting. 1.x menjalankan SQLite dalam mode WAL, sehingga kontainer 1.x yang dihentikan secara rutin meninggalkan sebagian besar data commit-nya di `snapotter.db-wal` di samping `snapotter.db` yang hampir kosong. Menyalin hanya `snapotter.db` akan menangkap database kosong dan diam-diam menghilangkan semuanya. Volume tersebut membawa `snapotter.db`, `snapotter.db-wal`, `snapotter.db-shm`, dan direktori `files/` Anda bersama-sama, dan semuanya harus berpindah sebagai satu set.
|
||||
|
||||
```bash
|
||||
# Adjust the volume name to match yours (see "Check your volume name" below).
|
||||
docker run --rm -v SnapOtter-data:/data -v "$PWD":/backup \
|
||||
alpine tar czf /backup/snapotter-1x-data.tgz -C /data .
|
||||
```
|
||||
|
||||
## Tingkatkan ke 1.17.2 terlebih dahulu {#upgrade-to-1-17-2-first}
|
||||
|
||||
Tingkatkan instalasi 1.x Anda ke rilis 1.x terbaru (1.17.2) sebelum berpindah ke 2.0. Itu memungkinkan 1.x menjalankan migrasi skema finalnya sendiri, sehingga 2.0 mengimpor dari skema yang diketahui dan lengkap. Meningkatkan dari 1.x yang lebih lama langsung ke 2.0 tidak didukung.
|
||||
|
||||
## Periksa nama volume Anda {#check-your-volume-name}
|
||||
|
||||
Importer hanya melihat data Anda jika stack 2.0 memasang volume yang sama dengan yang digunakan instalasi 1.x Anda. Nama volume Docker sensitif huruf besar-kecil, dan cuplikan README yang lebih lama menggunakan `snapotter-data` huruf kecil sementara file Compose menggunakan `SnapOtter-data`. Pastikan mana yang Anda miliki:
|
||||
|
||||
```bash
|
||||
docker volume ls | grep -i snapotter
|
||||
```
|
||||
|
||||
Gunakan nama persis itu dalam konfigurasi 2.0 Anda.
|
||||
|
||||
## Jalur A: kontainer tunggal (tercepat) {#path-a-single-container-quickest}
|
||||
|
||||
Jika Anda menjalankan SnapOtter dengan satu `docker run`, teruskan melakukannya. 2.0 mem-boot PostgreSQL dan Redis tertanam di dalam kontainer saat Anda tidak menyetel `DATABASE_URL` atau `REDIS_URL`, dan secara otomatis mendeteksi serta mengimpor `/data/snapotter.db` pada boot pertama.
|
||||
|
||||
```bash
|
||||
docker run -d --name snapotter -p 1349:1349 \
|
||||
-v SnapOtter-data:/data \
|
||||
snapotter/snapotter:latest
|
||||
```
|
||||
|
||||
Pantau log untuk baris seperti:
|
||||
|
||||
```
|
||||
Imported 1.x SQLite database: {"tables":{"users":2,"teams":1,...},"blobs":{"present":1,"missing":0}}
|
||||
```
|
||||
|
||||
Selesai. Masuk dengan kredensial yang sudah ada.
|
||||
|
||||
## Jalur B: Compose (disarankan untuk produksi) {#path-b-compose-recommended-for-production}
|
||||
|
||||
Stack Compose 2.0 menjalankan tiga layanan (app, Postgres, Redis). Gunakan kembali volume `/data` 1.x Anda untuk layanan app. Aplikasi secara otomatis mendeteksi `/data/snapotter.db` dan mengimpornya ke Postgres pada boot pertama.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
SnapOtter:
|
||||
image: snapotter/snapotter:latest
|
||||
volumes:
|
||||
- SnapOtter-data:/data # your existing 1.x volume
|
||||
- SnapOtter-workspace:/tmp/workspace
|
||||
environment:
|
||||
- DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
|
||||
- REDIS_URL=redis://:snapotter@redis:6379
|
||||
# ...
|
||||
```
|
||||
|
||||
Jika Anda lebih suka menunjuk ke database lama secara eksplisit, setel `SQLITE_MIGRATE_PATH=/data/snapotter.db`. Jalur eksplisit selalu menang atas deteksi otomatis.
|
||||
|
||||
## Pratinjau impor terlebih dahulu (opsional) {#preview-the-import-first-optional}
|
||||
|
||||
Untuk melihat persis apa yang akan diimpor tanpa menulis apa pun, jalankan dry run terhadap file database Anda:
|
||||
|
||||
```bash
|
||||
pnpm --filter @snapotter/api migrate:sqlite -- /path/to/snapotter.db --dry-run
|
||||
```
|
||||
|
||||
Ini mencetak jumlah baris per tabel, berapa banyak file pustaka tersimpan yang ditemukan di disk, dan status pekerjaan apa pun yang akan dinormalisasi. Tidak memerlukan Postgres yang berjalan.
|
||||
|
||||
## Apa yang ikut terbawa, dan apa yang tidak {#what-carries-over-and-what-does-not}
|
||||
|
||||
Yang ikut terbawa:
|
||||
|
||||
- Pengguna, dan kemampuan untuk masuk. Hash kata sandi tidak berubah, jadi username dan kata sandi yang sama tetap berfungsi.
|
||||
- Tim, pengaturan (termasuk identitas instans Anda), peran, kunci API (tetap berfungsi), dan pipeline tersimpan.
|
||||
- Catatan riwayat pekerjaan.
|
||||
- Pustaka file tersimpan Anda, baik catatannya maupun file sebenarnya, karena `/data/files` dipertahankan pada volume.
|
||||
|
||||
Yang tidak ikut terbawa:
|
||||
|
||||
- Sesi login. Semua orang masuk sekali setelah peningkatan. Kredensial tidak berubah, jadi ini hanya satu kali login ulang, tidak lebih.
|
||||
- File input dan output dari pekerjaan pemrosesan lama. File-file itu berada di ruang kerja sementara dan hilang secara desain. Catatan riwayat pekerjaan tetap ada.
|
||||
- Flag persetujuan analitik per pengguna dari 1.x, yang tidak memiliki padanan di 2.0 (analitik 2.0 adalah pengaturan tingkat instans).
|
||||
|
||||
## Menonaktifkan impor {#turning-the-import-off}
|
||||
|
||||
Jika Anda sengaja menginginkan database baru meskipun ada `snapotter.db` pada volume, setel `SQLITE_MIGRATE_PATH=off`.
|
||||
|
||||
## Jika Anda sudah memiliki data di instans 2.0 {#if-you-already-have-data-in-the-2-0-instance}
|
||||
|
||||
Importer hanya berjalan pada database kosong. Jika Anda memulai 2.0 dari awal (membuat data), lalu kemudian memasang `snapotter.db` lama, 2.0 akan mendeteksinya tetapi tidak akan mengimpor, karena menggabungkan dua kumpulan data dapat menyebabkan tabrakan pada ID. Anda akan melihat peringatan di log. Untuk mengimpor data 1.x, Anda memerlukan instans kosong:
|
||||
|
||||
- Jika instans 2.0 hanya berisi admin bawaan (Anda belum benar-benar menggunakannya), hentikan stack, hapus volume Postgres (`SnapOtter-pgdata`), dan boot lagi dengan `/data` lama tersedia. Ini akan mengimpor dengan bersih. Ini hanya menghapus data Postgres sekali pakai, bukan database 1.x Anda.
|
||||
- Jika instans 2.0 berisi data nyata yang ingin Anda pertahankan, kedua kumpulan data tidak dapat digabungkan otomatis. Ekspor apa yang Anda butuhkan dan impor data 1.x ke deployment baru yang terpisah.
|
||||
|
||||
## Melakukan rollback {#rolling-back}
|
||||
|
||||
Peningkatan tidak pernah mengubah atau menghapus `snapotter.db` 1.x Anda. Jika Anda perlu kembali ke 1.x, deploy ulang image 1.x terhadap volume yang sama. Apa pun yang Anda buat di 2.0 setelah peningkatan berada di Postgres dan tidak akan ada di database 1.x, jadi lakukan rollback segera jika memang akan melakukannya.
|
||||
@@ -0,0 +1,264 @@
|
||||
---
|
||||
description: "Kelola pengguna, peran bawaan dan kustom, izin, kunci API, tim, sesi, dan log audit di SnapOtter."
|
||||
i18n_source_hash: 5e28af686c96
|
||||
i18n_provenance: human
|
||||
i18n_output_hash: 1ffa8126804f
|
||||
---
|
||||
|
||||
# Pengguna, Peran & Izin {#users-roles-permissions}
|
||||
|
||||
SnapOtter hadir dengan tiga peran bawaan, 17 izin granular, dan dukungan untuk peran kustom dengan kontrol akses per alat opsional. Halaman ini membahas model otorisasi lengkap, pembatasan cakupan kunci API, manajemen tim, dan pencatatan audit.
|
||||
|
||||
::: tip Halaman terkait
|
||||
[OIDC / SSO](/id/guide/oidc) | [SAML SSO](/id/guide/saml) | [SCIM Provisioning](/id/guide/scim) | [Security & Hardening](/id/guide/security)
|
||||
:::
|
||||
|
||||
## Pengguna {#users}
|
||||
|
||||
### Membuat pengguna {#creating-users}
|
||||
|
||||
Admin dapat membuat pengguna melalui panel admin atau endpoint `POST /api/auth/register`. Setiap pengguna memiliki username, peran, penetapan tim, dan alamat email opsional.
|
||||
|
||||
### Admin bawaan {#default-admin}
|
||||
|
||||
Pada startup pertama SnapOtter membuat akun admin bawaan. Kredensialnya berasal dari variabel lingkungan:
|
||||
|
||||
| Variabel | Bawaan | Deskripsi |
|
||||
|---|---|---|
|
||||
| `DEFAULT_USERNAME` | `admin` | Username untuk akun admin awal |
|
||||
| `DEFAULT_PASSWORD` | `admin` | Kata sandi untuk akun admin awal |
|
||||
|
||||
Admin bawaan diwajibkan mengubah kata sandinya saat login pertama.
|
||||
|
||||
### Penyedia autentikasi {#authentication-providers}
|
||||
|
||||
Pengguna dapat terautentikasi melalui beberapa metode:
|
||||
|
||||
- **Lokal** - username dan kata sandi disimpan di database SnapOtter
|
||||
- **OIDC** - penyedia OpenID Connect apa pun (lihat [OIDC / SSO](/id/guide/oidc))
|
||||
- **SAML** - penyedia identitas SAML 2.0 (lihat [SAML SSO](/id/guide/saml))
|
||||
- **SCIM** - provisioning otomatis dari penyedia identitas (lihat [SCIM Provisioning](/id/guide/scim))
|
||||
|
||||
### Menonaktifkan autentikasi {#disabling-authentication}
|
||||
|
||||
Setel `AUTH_ENABLED=false` untuk menonaktifkan autentikasi sepenuhnya. Dalam mode ini, pengguna anonim sintetis dengan peran `admin` digunakan untuk semua permintaan. Tidak ada login yang diperlukan.
|
||||
|
||||
::: warning
|
||||
Menonaktifkan autentikasi memberikan akses admin penuh kepada siapa pun yang dapat menjangkau instans. Hanya gunakan ini di lingkungan tepercaya.
|
||||
:::
|
||||
|
||||
## Peran bawaan {#built-in-roles}
|
||||
|
||||
SnapOtter menyertakan tiga peran bawaan. Peran-peran ini tidak dapat diubah atau dihapus.
|
||||
|
||||
### Admin {#admin}
|
||||
|
||||
Semua 17 izin. Kontrol penuh atas instans.
|
||||
|
||||
`tools:use` `files:own` `files:all` `apikeys:own` `apikeys:all` `pipelines:own` `pipelines:all` `settings:read` `settings:write` `users:manage` `teams:manage` `features:manage` `system:health` `audit:read` `compliance:manage` `webhooks:manage` `security:manage`
|
||||
|
||||
### Editor {#editor}
|
||||
|
||||
7 izin. Dapat menggunakan semua alat dan mengelola semua file serta pipeline, tetapi tidak dapat mengakses fungsi admin.
|
||||
|
||||
`tools:use` `files:own` `files:all` `apikeys:own` `pipelines:own` `pipelines:all` `settings:read`
|
||||
|
||||
### User {#user}
|
||||
|
||||
5 izin. Dapat menggunakan alat dan mengelola sumber dayanya sendiri.
|
||||
|
||||
`tools:use` `files:own` `apikeys:own` `pipelines:own` `settings:read`
|
||||
|
||||
## Referensi izin {#permissions-reference}
|
||||
|
||||
| Izin | Deskripsi |
|
||||
|---|---|
|
||||
| `tools:use` | Menggunakan alat pemrosesan apa pun |
|
||||
| `files:own` | Melihat dan mengelola file sendiri |
|
||||
| `files:all` | Melihat dan mengelola file semua pengguna |
|
||||
| `apikeys:own` | Membuat dan mengelola kunci API sendiri |
|
||||
| `apikeys:all` | Melihat kunci API semua pengguna |
|
||||
| `pipelines:own` | Membuat dan mengelola pipeline sendiri |
|
||||
| `pipelines:all` | Melihat dan mengelola pipeline semua pengguna |
|
||||
| `settings:read` | Melihat pengaturan instans |
|
||||
| `settings:write` | Mengubah pengaturan instans |
|
||||
| `users:manage` | Membuat, memperbarui, dan menghapus akun pengguna |
|
||||
| `teams:manage` | Membuat, memperbarui, dan menghapus tim |
|
||||
| `features:manage` | Menginstal dan mengelola bundel fitur AI |
|
||||
| `system:health` | Mengakses endpoint health dan readiness |
|
||||
| `audit:read` | Melihat log audit dan mendaftar peran |
|
||||
| `compliance:manage` | Mengelola siklus hidup GDPR dan fitur kepatuhan |
|
||||
| `webhooks:manage` | Mengonfigurasi webhook keluar |
|
||||
| `security:manage` | Mengelola pengaturan keamanan (daftar izin IP, penegakan SSO) |
|
||||
|
||||
## Peran kustom {#custom-roles}
|
||||
|
||||
Admin dengan izin `security:manage` dapat membuat peran kustom melalui panel admin atau API peran. Mendaftar peran memerlukan `audit:read`.
|
||||
|
||||
### Membuat peran kustom {#creating-a-custom-role}
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:1349/api/v1/roles \
|
||||
-H "Authorization: Bearer si_..." \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "reviewer",
|
||||
"description": "Can use tools and view all files",
|
||||
"permissions": ["tools:use", "files:own", "files:all", "settings:read"]
|
||||
}'
|
||||
```
|
||||
|
||||
Nama peran harus 2-30 karakter, alfanumerik huruf kecil dengan tanda hubung dan garis bawah.
|
||||
|
||||
### Izin yang dicadangkan untuk admin {#admin-reserved-permissions}
|
||||
|
||||
Tiga izin dicadangkan untuk peran bawaan dan tidak dapat diberikan ke peran kustom:
|
||||
|
||||
- `compliance:manage`
|
||||
- `webhooks:manage`
|
||||
- `security:manage`
|
||||
|
||||
API peran menolak permintaan apa pun yang menyertakan izin-izin ini. Hanya peran `admin` bawaan yang memiliki akses ke izin-izin tersebut.
|
||||
|
||||
### Izin tingkat alat {#tool-level-permissions}
|
||||
|
||||
Peran kustom dapat secara opsional membatasi alat mana yang boleh diakses pengguna. Dua mode tersedia:
|
||||
|
||||
| Mode | Perilaku | Persyaratan lisensi |
|
||||
|---|---|---|
|
||||
| `category` | Membatasi berdasarkan modalitas (image, video, audio, document, file) | Tidak ada (gratis) |
|
||||
| `tool` | Membatasi berdasarkan ID alat individual | Memerlukan fitur enterprise `per_tool_permissions` |
|
||||
|
||||
Ketika mode `tool` disetel tetapi fitur enterprise tidak tersedia, SnapOtter menurunkan secara mulus dan mengizinkan akses ke semua alat.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "image-only",
|
||||
"permissions": ["tools:use", "files:own"],
|
||||
"toolPermissions": {
|
||||
"mode": "category",
|
||||
"allowed": ["image"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Menghapus peran kustom {#deleting-a-custom-role}
|
||||
|
||||
Saat peran kustom dihapus, semua pengguna yang ditetapkan padanya secara otomatis ditetapkan ulang ke peran `user`.
|
||||
|
||||
## Tim {#teams}
|
||||
|
||||
Tim mengelompokkan pengguna untuk manajemen penyimpanan dan retensi. Sebuah tim `Default` dibuat pada startup pertama.
|
||||
|
||||
| Bidang | Tipe | Deskripsi |
|
||||
|---|---|---|
|
||||
| `name` | string | Nama tim unik (1-50 karakter) |
|
||||
| `storageQuota` | number | Batas penyimpanan per tim dalam byte (berfungsi tanpa enterprise) |
|
||||
| `retentionHours` | number | Hapus otomatis output setelah sekian jam (memerlukan `team_retention_overrides`, enterprise) |
|
||||
| `legalHold` | boolean | Mencegah penghapusan otomatis file anggota tim (memerlukan `legal_hold`, enterprise) |
|
||||
|
||||
::: info
|
||||
Tim `Default` tidak dapat dihapus. Tim yang masih memiliki anggota tidak dapat dihapus. Tetapkan ulang anggota terlebih dahulu.
|
||||
:::
|
||||
|
||||
## Kunci API {#api-keys}
|
||||
|
||||
Pengguna dapat membuat kunci API untuk akses programatik. Setiap kunci menggunakan awalan `si_` dan hanya ditampilkan sekali saat pembuatan.
|
||||
|
||||
### Izin bercakupan {#scoped-permissions}
|
||||
|
||||
Kunci API dapat secara opsional membawa array `permissions`. Saat disetel, izin efektif untuk suatu permintaan adalah **irisan** dari izin peran pengguna dan izin bercakupan milik kunci. Ini berarti kunci API tidak pernah dapat melampaui izin pengguna itu sendiri.
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:1349/api/v1/api-keys \
|
||||
-H "Authorization: Bearer si_..." \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "CI pipeline key",
|
||||
"permissions": ["tools:use", "files:own"],
|
||||
"expiresAt": "2027-01-01T00:00:00Z"
|
||||
}'
|
||||
```
|
||||
|
||||
### Kedaluwarsa {#expiration}
|
||||
|
||||
Kunci menerima timestamp `expiresAt` opsional. Kunci yang kedaluwarsa ditolak pada saat autentikasi.
|
||||
|
||||
## Log audit {#audit-log}
|
||||
|
||||
SnapOtter mencatat peristiwa yang relevan dengan keamanan dalam log audit terstruktur yang disimpan di tabel database `audit_log`.
|
||||
|
||||
### Melihat log audit {#viewing-the-audit-log}
|
||||
|
||||
```
|
||||
GET /api/v1/audit-log?page=1&limit=50&action=LOGIN_FAILED&from=2026-01-01T00:00:00Z&to=2026-12-31T23:59:59Z
|
||||
```
|
||||
|
||||
Memerlukan izin `audit:read`. Mendukung paginasi (`page`, `limit`) dan filter (`action`, `ip`, `from`, `to`).
|
||||
|
||||
### Pengauditan operasi alat {#tool-operation-auditing}
|
||||
|
||||
::: warning
|
||||
Peristiwa `TOOL_EXECUTED` **tidak** dicatat secara bawaan. Peristiwa ini bersifat opt-in melalui salah satu dari dua jalur:
|
||||
|
||||
1. Setel pengaturan admin `auditToolOperations` ke `true`.
|
||||
2. Miliki lisensi aktif dengan fitur `audit_export` (tersedia pada paket team dan enterprise).
|
||||
|
||||
Tanpa salah satu dari ini, eksekusi alat individual tidak dicatat dalam log audit.
|
||||
:::
|
||||
|
||||
### Mengekspor {#exporting}
|
||||
|
||||
```
|
||||
GET /api/v1/enterprise/audit/export?format=csv&from=2026-01-01T00:00:00Z
|
||||
```
|
||||
|
||||
Memerlukan izin `audit:read` dan fitur enterprise `audit_export` (tersedia pada paket team dan enterprise). Mendukung format CSV dan JSON, difilter berdasarkan `action`, `actorId`, `targetType`, `targetId`, `from`, dan `to`.
|
||||
|
||||
### Penandatanganan anti-manipulasi {#tamper-resistant-signing}
|
||||
|
||||
Saat diaktifkan, setiap entri log audit ditandatangani dengan HMAC yang diturunkan dari `DATA_ENCRYPTION_KEY`. Ini memerlukan:
|
||||
|
||||
1. Menyetel `DATA_ENCRYPTION_KEY` di lingkungan Anda.
|
||||
2. Mengaktifkan pengaturan admin `tamperResistantAudit`.
|
||||
3. Lisensi enterprise dengan fitur `tamper_resistant_audit`.
|
||||
|
||||
### Retensi {#retention}
|
||||
|
||||
Setel `AUDIT_RETENTION_DAYS` untuk secara otomatis membersihkan entri lama. Bawaannya adalah `0`, yang berarti entri disimpan tanpa batas waktu.
|
||||
|
||||
### Referensi peristiwa {#event-reference}
|
||||
|
||||
| Peristiwa | Kategori |
|
||||
|---|---|
|
||||
| `LOGIN_SUCCESS`, `LOGIN_FAILED` | Authentication |
|
||||
| `OIDC_LOGIN_SUCCESS`, `OIDC_LOGIN_FAILED` | Authentication |
|
||||
| `SAML_LOGIN_SUCCESS`, `SAML_LOGIN_FAILED` | Authentication |
|
||||
| `LOGOUT` | Authentication |
|
||||
| `USER_CREATED`, `USER_UPDATED`, `USER_DELETED` | User management |
|
||||
| `PASSWORD_CHANGED`, `PASSWORD_RESET` | User management |
|
||||
| `MFA_ENROLLED`, `MFA_DISABLED`, `MFA_VERIFIED`, `MFA_VERIFY_FAILED` | MFA |
|
||||
| `MFA_CHALLENGE_ISSUED`, `MFA_RECOVERY_USED`, `MFA_RESET` | MFA |
|
||||
| `ROLE_CREATED`, `ROLE_UPDATED`, `ROLE_DELETED` | Roles |
|
||||
| `API_KEY_CREATED`, `API_KEY_DELETED` | API keys |
|
||||
| `SETTINGS_UPDATED`, `IP_ALLOWLIST_UPDATED` | Settings |
|
||||
| `FILE_UPLOADED`, `FILE_DELETED` | Files |
|
||||
| `TOOL_EXECUTED` | Tools (opt-in) |
|
||||
| `SCIM_USER_PROVISIONED`, `SCIM_USER_UPDATED`, `SCIM_USER_DEPROVISIONED` | SCIM |
|
||||
| `SCIM_GROUP_SYNCED` | SCIM |
|
||||
| `LEGAL_HOLD_APPLIED`, `LEGAL_HOLD_RELEASED` | Compliance |
|
||||
| `GDPR_EXPORT_INITIATED`, `GDPR_USER_PURGED`, `GDPR_TEAM_PURGED` | Compliance |
|
||||
| `CONFIG_EXPORTED`, `CONFIG_IMPORTED` | Configuration |
|
||||
|
||||
## Manajemen sesi {#session-management}
|
||||
|
||||
Sesi berbasis cookie, dikendalikan oleh `SESSION_DURATION_HOURS` (bawaan: 168 jam / 7 hari).
|
||||
|
||||
### Perubahan peran membatalkan sesi {#role-changes-invalidate-sessions}
|
||||
|
||||
Saat admin mengubah peran pengguna, semua sesi aktif pengguna tersebut dihapus. Pengguna harus login lagi untuk mendapatkan izin barunya.
|
||||
|
||||
### Pengaman keamanan {#safety-guards}
|
||||
|
||||
- **Perlindungan admin terakhir**: admin terakhir yang tersisa tidak dapat diturunkan ke peran yang lebih rendah. API mengembalikan error jika Anda mencoba.
|
||||
- **Pencegahan hapus diri**: admin tidak dapat menghapus akunnya sendiri melalui API.
|
||||
Reference in New Issue
Block a user