docs: migrate legacy docs to official site
This commit is contained in:
+12
-59
@@ -28,65 +28,6 @@ export default defineConfig({
|
||||
label: "English",
|
||||
lang: "en",
|
||||
},
|
||||
es: {
|
||||
label: "Español",
|
||||
lang: "es",
|
||||
link: "/es/",
|
||||
title: "OxiCloud",
|
||||
description: "Almacenamiento en la nube autoalojado, calendario y contactos — increíblemente rápido",
|
||||
themeConfig: {
|
||||
nav: [
|
||||
{ text: "Inicio", link: "/es/" },
|
||||
{ text: "Guía", link: "/es/guide/" },
|
||||
{ text: "Configuración", link: "/es/config/" },
|
||||
{ text: "FAQ", link: "/es/faq" },
|
||||
],
|
||||
editLink: {
|
||||
pattern: "https://github.com/DioCrafts/OxiCloud/tree/main/docs/:path",
|
||||
text: "Editar esta página en GitHub",
|
||||
},
|
||||
sidebar: {
|
||||
"/es/": [
|
||||
{
|
||||
text: "Introducción",
|
||||
items: [
|
||||
{ text: "¿Qué es OxiCloud?", link: "/es/guide/" },
|
||||
{ text: "Inicio Rápido", link: "/es/guide/installation" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Configuración",
|
||||
items: [
|
||||
{ text: "Despliegue & Docker", link: "/es/config/deployment" },
|
||||
{ text: "Variables de Entorno", link: "/es/config/env" },
|
||||
{ text: "OIDC / SSO", link: "/es/config/oidc" },
|
||||
{ text: "WOPI (Office)", link: "/es/config/wopi" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Características",
|
||||
items: [
|
||||
{ text: "WebDAV", link: "/es/guide/webdav" },
|
||||
{ text: "CalDAV & CardDAV", link: "/es/guide/caldav-carddav" },
|
||||
{ text: "Subida Chunked", link: "/es/guide/chunked-uploads" },
|
||||
{ text: "Deduplicación", link: "/es/guide/deduplication" },
|
||||
{ text: "Búsqueda", link: "/es/guide/search" },
|
||||
{ text: "Papelera", link: "/es/guide/trash" },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Arquitectura",
|
||||
items: [
|
||||
{ text: "Arquitectura Interna", link: "/es/architecture/" },
|
||||
{ text: "Caché", link: "/es/architecture/caching" },
|
||||
{ text: "Cuotas de Almacenamiento", link: "/es/architecture/storage-quotas" },
|
||||
],
|
||||
},
|
||||
{ text: "FAQ", link: "/es/faq" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
head: [
|
||||
@@ -126,7 +67,10 @@ export default defineConfig({
|
||||
items: [
|
||||
{ text: "Deployment & Docker", link: "/config/deployment" },
|
||||
{ text: "Environment Variables", link: "/config/env" },
|
||||
{ text: "Authentication", link: "/config/authentication" },
|
||||
{ text: "OIDC / SSO", link: "/config/oidc" },
|
||||
{ text: "OIDC Config Examples", link: "/config/oidc-config-examples" },
|
||||
{ text: "Admin Settings", link: "/config/admin-settings" },
|
||||
{ text: "WOPI (Office Editing)", link: "/config/wopi" },
|
||||
],
|
||||
},
|
||||
@@ -135,10 +79,16 @@ export default defineConfig({
|
||||
items: [
|
||||
{ text: "WebDAV", link: "/guide/webdav" },
|
||||
{ text: "CalDAV & CardDAV", link: "/guide/caldav-carddav" },
|
||||
{ text: "DAV Client Setup", link: "/guide/dav-client-setup" },
|
||||
{ text: "Chunked Uploads", link: "/guide/chunked-uploads" },
|
||||
{ text: "Batch Operations", link: "/guide/batch-operations" },
|
||||
{ text: "Deduplication", link: "/guide/deduplication" },
|
||||
{ text: "Favorites & Recent", link: "/guide/favorites-and-recent" },
|
||||
{ text: "Search", link: "/guide/search" },
|
||||
{ text: "Thumbnails & Transcoding", link: "/guide/thumbnails-and-transcoding" },
|
||||
{ text: "Trash & Recycle Bin", link: "/guide/trash" },
|
||||
{ text: "ZIP & Compression", link: "/guide/zip-and-compression" },
|
||||
{ text: "Internationalization", link: "/guide/i18n" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -146,6 +96,9 @@ export default defineConfig({
|
||||
items: [
|
||||
{ text: "Internal Architecture", link: "/architecture/" },
|
||||
{ text: "Caching", link: "/architecture/caching" },
|
||||
{ text: "Storage Safety", link: "/architecture/file-system-safety" },
|
||||
{ text: "Database Transactions", link: "/architecture/database-transactions" },
|
||||
{ text: "Share Integration", link: "/architecture/share-integration" },
|
||||
{ text: "Storage Quotas", link: "/architecture/storage-quotas" },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Database Transactions
|
||||
|
||||
OxiCloud uses explicit PostgreSQL transactions for multi-step operations that must either commit together or fail together.
|
||||
|
||||
## ACID Guarantees
|
||||
|
||||
- Atomicity: all work succeeds or the entire transaction rolls back
|
||||
- Consistency: constraints and invariants remain valid before and after commit
|
||||
- Isolation: concurrent work behaves predictably
|
||||
- Durability: committed writes survive process and system failures
|
||||
|
||||
## Transaction Helper
|
||||
|
||||
The PostgreSQL repositories use a helper like `with_transaction` to standardize the transaction lifecycle:
|
||||
|
||||
```rust
|
||||
pub async fn with_transaction<F, T, E>(
|
||||
pool: &Arc<PgPool>,
|
||||
operation_name: &str,
|
||||
operation: F,
|
||||
) -> Result<T, E>
|
||||
where
|
||||
F: for<'c> FnOnce(&'c mut Transaction<'_, Postgres>) -> futures::future::BoxFuture<'c, Result<T, E>>,
|
||||
E: From<SqlxError> + std::fmt::Display,
|
||||
{ /* ... */ }
|
||||
```
|
||||
|
||||
That wrapper handles begin, commit, rollback, and lifecycle logging so repository code can focus on the actual domain operation.
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### User management
|
||||
|
||||
Transactions keep related user changes together, such as creating a user and attaching the dependent records required for a valid account.
|
||||
|
||||
### Session management
|
||||
|
||||
Session creation and session revocation can update multiple tables in a single logical step, which avoids stale or mismatched security state.
|
||||
|
||||
### File and folder workflows
|
||||
|
||||
Moves, renames, trash operations, and other multi-step metadata changes rely on transactions so the tree stays consistent.
|
||||
|
||||
## Isolation Levels
|
||||
|
||||
OxiCloud can use different isolation levels depending on the operation.
|
||||
|
||||
| Level | Use case |
|
||||
| --- | --- |
|
||||
| `Read Committed` | Default application work |
|
||||
| `Repeatable Read` | Stable reads during a longer unit of work |
|
||||
| `Serializable` | Highest safety for conflict-prone critical operations |
|
||||
|
||||
Higher isolation can introduce retries or contention, so it should be reserved for the few flows that need it.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Keep transactions short
|
||||
- Avoid heavy I/O inside a transaction when possible
|
||||
- Group only operations that must commit together
|
||||
- Choose the lowest isolation level that preserves correctness
|
||||
- Log and surface rollback causes clearly
|
||||
|
||||
## Why It Matters
|
||||
|
||||
- Prevents partial metadata updates
|
||||
- Keeps concurrent user activity predictable
|
||||
- Reduces race conditions in critical operations
|
||||
- Makes failures recoverable and easier to reason about
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [Storage Safety](/architecture/file-system-safety)
|
||||
- [Internal Architecture](/architecture/)
|
||||
@@ -0,0 +1,77 @@
|
||||
# Storage Safety
|
||||
|
||||
OxiCloud protects file integrity with two layers working together:
|
||||
|
||||
- PostgreSQL transactions for metadata
|
||||
- Atomic blob writes for content
|
||||
|
||||
The result is a simple guarantee: operations either complete fully or fail cleanly enough to recover without corrupting user data.
|
||||
|
||||
## Storage Model
|
||||
|
||||
- Metadata such as names, folders, MIME types, quotas, and trash state lives in PostgreSQL
|
||||
- File content is stored as content-addressed blobs under the storage backend
|
||||
- Deduplication metadata is tracked separately so multiple files can reference the same content safely
|
||||
|
||||
## Metadata Safety
|
||||
|
||||
PostgreSQL protects metadata with ACID transactions.
|
||||
|
||||
- Single-row writes are atomic by default
|
||||
- Multi-step operations use explicit transactions
|
||||
- Foreign keys prevent orphaned references
|
||||
- Unique constraints prevent illegal duplicates in the same scope
|
||||
- Trash uses soft-delete semantics until permanent deletion is requested
|
||||
|
||||
## Content Safety
|
||||
|
||||
Blob writes rely on an atomic write pattern:
|
||||
|
||||
1. Write new content to a temporary file
|
||||
2. `fsync` the file to push data and metadata to durable storage
|
||||
3. Rename the temp file into its final content-addressed path
|
||||
4. Sync parent directory metadata when needed
|
||||
|
||||
This prevents partially written blobs from appearing as valid stored content.
|
||||
|
||||
## Deduplication Notes
|
||||
|
||||
OxiCloud's deduplication pipeline uses BLAKE3 hashing and chunk manifest tracking. The storage layer can therefore reuse identical content while still keeping metadata changes transactional.
|
||||
|
||||
If content is stored successfully but the later metadata transaction fails, the content may remain as an unreferenced blob. That is a space leak, not a consistency leak, and can be cleaned up later.
|
||||
|
||||
## Upload Flow
|
||||
|
||||
```text
|
||||
1. Receive content and spool it safely to storage
|
||||
2. Finalize the content-addressed blob write
|
||||
3. Begin metadata transaction
|
||||
4. Insert or update file metadata in PostgreSQL
|
||||
5. Commit
|
||||
```
|
||||
|
||||
If step 1 or 2 fails, no metadata is committed. If step 4 or 5 fails, metadata rolls back and the storage layer can clean up unreferenced content later.
|
||||
|
||||
## Delete Flow
|
||||
|
||||
```text
|
||||
1. Begin metadata transaction
|
||||
2. Remove or soft-delete the metadata row
|
||||
3. Commit
|
||||
4. Decrement blob references and remove physical content when the refcount reaches zero
|
||||
```
|
||||
|
||||
If the metadata transaction fails, the physical file is not considered deleted. If the refcount cleanup fails, the system may keep extra content on disk, but user-visible metadata remains correct.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
- Crash resilience during uploads and deletes
|
||||
- Safe recovery after power loss or host restarts
|
||||
- Clean separation between metadata correctness and background storage cleanup
|
||||
- Predictable behavior for trash, deduplication, and shared storage backends
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [Internal Architecture](/architecture/)
|
||||
- [Database Transactions](/architecture/database-transactions)
|
||||
- [Storage Quotas](/architecture/storage-quotas)
|
||||
@@ -37,6 +37,17 @@ All cross-layer dependencies point **inward** via trait-based ports. The DI cont
|
||||
8. **ZIP service** (last, depends on file & folder services)
|
||||
9. **Assemble `AppState`**
|
||||
|
||||
## AppState Shape
|
||||
|
||||
The assembled `AppState` groups the application into a few stable buckets:
|
||||
|
||||
- `core` for cross-cutting runtime services such as path resolution, caching, chunked uploads, deduplication, compression, thumbnails, and ZIP handling
|
||||
- `repositories` for PostgreSQL-backed folder, file, trash, and i18n persistence
|
||||
- `applications` for the use-case layer exposed to handlers
|
||||
- optional auth, admin, trash, share, favorites, recent, storage usage, calendar, and contact services when those features are enabled
|
||||
|
||||
This lets handlers depend on stable interfaces while the concrete implementation details stay inside the DI container.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Share Integration
|
||||
|
||||
OxiCloud supports public file and folder sharing through signed share links. A share can be public, password-protected, time-limited, or scoped by permissions.
|
||||
|
||||
## What a Share Contains
|
||||
|
||||
A share record tracks:
|
||||
|
||||
- The shared item ID and whether it is a file or folder
|
||||
- A public token used in the share URL
|
||||
- Optional password protection
|
||||
- Optional expiration timestamp
|
||||
- Permissions for read, write, and reshare
|
||||
- The creator and access count
|
||||
|
||||
## Public and Private Routes
|
||||
|
||||
### Authenticated management routes
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `POST` | `/api/shares/` | Create a new share |
|
||||
| `GET` | `/api/shares/` | List current user's shares |
|
||||
| `GET` | `/api/shares/{id}` | Fetch one share |
|
||||
| `PUT` | `/api/shares/{id}` | Update permissions, password, or expiration |
|
||||
| `DELETE` | `/api/shares/{id}` | Delete a share |
|
||||
|
||||
### Public access routes
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/s/{token}` | Access a shared item |
|
||||
| `POST` | `/api/s/{token}/verify` | Verify a password-protected share |
|
||||
|
||||
## Service Responsibilities
|
||||
|
||||
The share service is responsible for:
|
||||
|
||||
- Validating that the underlying file or folder exists
|
||||
- Generating unique share IDs and public tokens
|
||||
- Enforcing password checks and expiration rules
|
||||
- Mapping domain permissions into API DTOs
|
||||
- Recording access counts
|
||||
|
||||
Share metadata is persisted separately from the file content itself. The shared resource still uses the normal storage model for files and folders.
|
||||
|
||||
## Example Workflow
|
||||
|
||||
### Creating a share link
|
||||
|
||||
1. A user selects a file or folder in the UI
|
||||
2. The frontend submits a request to `/api/shares/`
|
||||
3. OxiCloud validates the target and requested permissions
|
||||
4. The backend generates a token and public URL
|
||||
5. The share metadata is saved and returned to the caller
|
||||
|
||||
### Opening a share link
|
||||
|
||||
1. A guest opens `/api/s/{token}`
|
||||
2. OxiCloud verifies the token and checks expiration
|
||||
3. If the share is password protected, the client verifies the password first
|
||||
4. Access is counted and the shared resource is returned according to the granted permissions
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Passwords are stored as hashes, never as plaintext
|
||||
- Expired shares are rejected before content access
|
||||
- Permissions are checked per action, not only when the share is created
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [OIDC / SSO](/config/oidc)
|
||||
- [Admin Settings](/config/admin-settings)
|
||||
- [Internal Architecture](/architecture/)
|
||||
@@ -15,6 +15,10 @@ OXICLOUD_ENABLE_USER_STORAGE_QUOTAS=true
|
||||
3. If the upload would exceed the quota, it's rejected with a `413 Payload Too Large` error
|
||||
4. Admins can view and set quotas via the admin panel or API
|
||||
|
||||
## Usage Calculation
|
||||
|
||||
The storage usage service recalculates logical usage from the user's home folder tree and sums file sizes recursively. Directory entries are skipped and the final value is written back to `auth.users.storage_used`.
|
||||
|
||||
## API
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
@@ -26,6 +30,14 @@ OXICLOUD_ENABLE_USER_STORAGE_QUOTAS=true
|
||||
|
||||
The admin panel (`/admin.html`) shows each user's current usage vs. quota with a visual progress bar.
|
||||
|
||||
The dashboard also exposes aggregate quota stats such as:
|
||||
|
||||
- total quota bytes
|
||||
- total used bytes
|
||||
- overall storage usage percent
|
||||
- users above 80% usage
|
||||
- users over quota
|
||||
|
||||
## Deduplication Interaction
|
||||
|
||||
Storage usage is calculated based on **logical file size** (what the user uploaded), not physical blob size. This means:
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# Admin Settings
|
||||
|
||||
OxiCloud exposes an admin API for runtime configuration, dashboard stats, and user administration. All routes live under `/api/admin` and require an authenticated admin JWT.
|
||||
|
||||
## Settings Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/admin/settings/oidc` | Read current OIDC settings |
|
||||
| `PUT` | `/api/admin/settings/oidc` | Save OIDC settings |
|
||||
| `POST` | `/api/admin/settings/oidc/test` | Test provider connectivity |
|
||||
| `GET` | `/api/admin/settings/general` | Read general server settings |
|
||||
|
||||
The OIDC runtime UI complements the base configuration described in [OIDC / SSO](/config/oidc) and the provider samples in [OIDC Config Examples](/config/oidc-config-examples).
|
||||
|
||||
## Dashboard Endpoint
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/admin/dashboard` | Read server statistics and feature state |
|
||||
|
||||
Typical dashboard fields include:
|
||||
|
||||
- server version
|
||||
- whether auth and OIDC are enabled
|
||||
- whether quotas are enabled
|
||||
- total, active, and admin user counts
|
||||
- quota usage totals and percentage
|
||||
|
||||
## User Management Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/admin/users` | List users |
|
||||
| `GET` | `/api/admin/users/{id}` | Get one user |
|
||||
| `DELETE` | `/api/admin/users/{id}` | Delete a user |
|
||||
| `PUT` | `/api/admin/users/{id}/role` | Change role |
|
||||
| `PUT` | `/api/admin/users/{id}/active` | Activate or deactivate a user |
|
||||
| `PUT` | `/api/admin/users/{id}/quota` | Update a storage quota |
|
||||
|
||||
### Built-in safety guards
|
||||
|
||||
- Admins cannot delete their own account
|
||||
- Admins cannot change their own role
|
||||
- Admins cannot deactivate themselves
|
||||
|
||||
## OIDC Settings Priority
|
||||
|
||||
When the same setting exists in multiple places, OxiCloud resolves it in this order:
|
||||
|
||||
1. Environment variables such as `OXICLOUD_OIDC_*`
|
||||
2. Values stored in the admin settings table
|
||||
3. Built-in defaults
|
||||
|
||||
If a value is overridden by environment variables, the admin API can expose that in the response so operators know why a saved value is not taking effect.
|
||||
|
||||
## Test Connection Example
|
||||
|
||||
```json
|
||||
{
|
||||
"issuer_url": "https://keycloak.example.com/realms/main"
|
||||
}
|
||||
```
|
||||
|
||||
Successful responses include discovered endpoints such as the authorization endpoint, token endpoint, and userinfo endpoint.
|
||||
|
||||
## Data Storage
|
||||
|
||||
Runtime settings are stored in `auth.admin_settings`.
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS auth.admin_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
is_secret BOOLEAN DEFAULT FALSE,
|
||||
updated_by VARCHAR(36),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [OIDC / SSO](/config/oidc)
|
||||
- [OIDC Config Examples](/config/oidc-config-examples)
|
||||
- [Environment Variables](/config/env)
|
||||
@@ -0,0 +1,72 @@
|
||||
# Authentication
|
||||
|
||||
OxiCloud ships with JWT-based authentication and Argon2id password hashing for local accounts. It also exposes status and OIDC-related auth endpoints under the same `/api/auth` namespace.
|
||||
|
||||
## Core Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| --- | --- | --- |
|
||||
| `POST` | `/api/auth/register` | Create a local user account |
|
||||
| `POST` | `/api/auth/login` | Exchange username and password for access and refresh tokens |
|
||||
| `POST` | `/api/auth/refresh` | Refresh the session tokens |
|
||||
| `GET` | `/api/auth/me` | Return the current authenticated user |
|
||||
| `PUT` | `/api/auth/change-password` | Change the current user's password |
|
||||
| `POST` | `/api/auth/logout` | Invalidate the current session |
|
||||
| `GET` | `/api/auth/status` | Return auth system state, including OIDC availability |
|
||||
|
||||
## OIDC Endpoints Under Auth
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/auth/oidc/providers` | List configured OIDC provider info |
|
||||
| `GET` | `/api/auth/oidc/authorize` | Build the authorization redirect URL |
|
||||
| `GET` | `/api/auth/oidc/callback` | Handle provider redirect callback |
|
||||
| `POST` | `/api/auth/oidc/exchange` | Exchange the auth code for OxiCloud session tokens |
|
||||
|
||||
## Example Flows
|
||||
|
||||
### Register
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"password": "SecurePassword123"
|
||||
}
|
||||
```
|
||||
|
||||
### Login
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "testuser",
|
||||
"password": "SecurePassword123"
|
||||
}
|
||||
```
|
||||
|
||||
Typical successful login response:
|
||||
|
||||
```json
|
||||
{
|
||||
"accessToken": "...",
|
||||
"refreshToken": "...",
|
||||
"expiresIn": 3600
|
||||
}
|
||||
```
|
||||
|
||||
### Current User
|
||||
|
||||
`GET /api/auth/me` returns the authenticated user's identity, role, and storage information.
|
||||
|
||||
## Security Model
|
||||
|
||||
- local passwords are hashed with Argon2id
|
||||
- access control is role-based (`admin` and `user`)
|
||||
- refresh tokens support session renewal without forcing frequent re-login
|
||||
- OIDC can coexist with local auth or disable password login entirely
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [OIDC / SSO](/config/oidc)
|
||||
- [Admin Settings](/config/admin-settings)
|
||||
- [Environment Variables](/config/env)
|
||||
@@ -16,14 +16,18 @@ The final image runs as non-root user `oxicloud` (UID/GID 1001). Exposed port: *
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17.4-alpine
|
||||
image: postgres:18.2-alpine3.23
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: oxicloud
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres # change in production!
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
networks:
|
||||
- oxicloud
|
||||
volumes:
|
||||
- pg_data:/var/lib/postgresql/data
|
||||
- ./db/schema.sql:/docker-entrypoint-initdb.d/schema.sql
|
||||
- pg_data:/var/lib/postgresql/
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
@@ -31,9 +35,15 @@ services:
|
||||
retries: 5
|
||||
|
||||
oxicloud:
|
||||
image: ghcr.io/diocrafts/oxicloud:latest
|
||||
image: diocrafts/oxicloud:latest
|
||||
restart: always
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8086:8086"
|
||||
networks:
|
||||
- oxicloud
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
@@ -42,11 +52,17 @@ services:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
networks:
|
||||
oxicloud:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
pg_data:
|
||||
storage_data:
|
||||
```
|
||||
|
||||
This example mirrors the repository's current `docker-compose.yml`. If you deploy from a registry-only setup, you can keep the `image:` line and remove the `build:` stanza.
|
||||
|
||||
## Kubernetes (Helm)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
+18
-1
@@ -1,6 +1,6 @@
|
||||
# Environment Variables
|
||||
|
||||
All variables use the `OXICLOUD_` prefix.
|
||||
Most runtime variables use the `OXICLOUD_` prefix. A few build-time or allocator variables do not.
|
||||
|
||||
## Server
|
||||
|
||||
@@ -19,6 +19,14 @@ All variables use the `OXICLOUD_` prefix.
|
||||
| `OXICLOUD_DB_CONNECTION_STRING` | `postgres://postgres:postgres@localhost:5432/oxicloud` | PostgreSQL connection string |
|
||||
| `OXICLOUD_DB_MAX_CONNECTIONS` | `20` | Max pool connections |
|
||||
| `OXICLOUD_DB_MIN_CONNECTIONS` | `5` | Min pool connections |
|
||||
| `OXICLOUD_DB_MAINTENANCE_MAX_CONNECTIONS` | `5` | Max connections in the isolated maintenance pool |
|
||||
| `OXICLOUD_DB_MAINTENANCE_MIN_CONNECTIONS` | `1` | Min connections in the isolated maintenance pool |
|
||||
|
||||
## Build-Time SQLx
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `DATABASE_URL` | — | Build-time database URL for SQLx compile-time checks |
|
||||
|
||||
## Authentication
|
||||
|
||||
@@ -68,6 +76,15 @@ See the [WOPI configuration guide](/config/wopi) for details.
|
||||
| `OXICLOUD_WOPI_TOKEN_TTL_SECS` | `86400` | Token lifetime |
|
||||
| `OXICLOUD_WOPI_LOCK_TTL_SECS` | `1800` | Lock expiration |
|
||||
|
||||
## Allocator Tuning
|
||||
|
||||
These variables are read directly by **mimalloc**, not by OxiCloud's config parser.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `MIMALLOC_PURGE_DELAY` | `0` | Delay in ms before freed memory is returned to the OS |
|
||||
| `MIMALLOC_ALLOW_LARGE_OS_PAGES` | `0` | Enable or disable large OS pages for allocations |
|
||||
|
||||
## Internal Defaults (not configurable via env)
|
||||
|
||||
| Parameter | Default |
|
||||
|
||||
@@ -6,6 +6,7 @@ OxiCloud is configured entirely via **environment variables** (no config files n
|
||||
|
||||
- [Deployment & Docker](/config/deployment) — Docker Compose, Kubernetes Helm chart, image details
|
||||
- [Environment Variables](/config/env) — complete reference of all `OXICLOUD_*` variables
|
||||
- [Authentication](/config/authentication) — JWT auth, login, refresh, password changes, and auth status
|
||||
- [OIDC / SSO](/config/oidc) — single sign-on with Keycloak, Authentik, Authelia, Google, Azure AD
|
||||
- [WOPI (Office Editing)](/config/wopi) — Collabora Online / OnlyOffice integration
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
# OIDC Config Examples
|
||||
|
||||
This page collects provider-specific OpenID Connect examples for OxiCloud. Use it together with the base reference in [OIDC / SSO](/config/oidc).
|
||||
|
||||
## Base Environment Variables
|
||||
|
||||
```bash
|
||||
OXICLOUD_OIDC_ENABLED=true
|
||||
OXICLOUD_OIDC_PROVIDER_NAME="Display Name"
|
||||
OXICLOUD_OIDC_ISSUER_URL="https://provider.example.com/realms/your-realm"
|
||||
OXICLOUD_OIDC_CLIENT_ID="your-client-id"
|
||||
OXICLOUD_OIDC_CLIENT_SECRET="your-client-secret"
|
||||
OXICLOUD_OIDC_REDIRECT_URI="https://your-oxicloud.example.com/api/auth/oidc/callback"
|
||||
OXICLOUD_OIDC_SCOPES="openid profile email"
|
||||
OXICLOUD_OIDC_FRONTEND_URL="https://your-oxicloud.example.com"
|
||||
OXICLOUD_OIDC_AUTO_PROVISION="true"
|
||||
OXICLOUD_OIDC_ADMIN_GROUPS="admin-group"
|
||||
OXICLOUD_OIDC_DISABLE_PASSWORD_LOGIN="false"
|
||||
```
|
||||
|
||||
## Authentik
|
||||
|
||||
1. Create an OAuth2 or OpenID Connect application for OxiCloud
|
||||
2. Register `https://your-oxicloud.example.com/api/auth/oidc/callback` as the callback URL
|
||||
3. Copy the generated client ID and client secret
|
||||
|
||||
```yaml
|
||||
services:
|
||||
oxicloud:
|
||||
image: diocrafts/oxicloud:latest
|
||||
environment:
|
||||
OXICLOUD_OIDC_ENABLED: "true"
|
||||
OXICLOUD_OIDC_PROVIDER_NAME: "Authentik"
|
||||
OXICLOUD_OIDC_ISSUER_URL: "https://authentik.example.com/application/o/oxicloud"
|
||||
OXICLOUD_OIDC_CLIENT_ID: "your-authentik-client-id"
|
||||
OXICLOUD_OIDC_CLIENT_SECRET: "your-authentik-client-secret"
|
||||
OXICLOUD_OIDC_REDIRECT_URI: "https://oxicloud.example.com/api/auth/oidc/callback"
|
||||
OXICLOUD_OIDC_SCOPES: "openid profile email"
|
||||
OXICLOUD_OIDC_FRONTEND_URL: "https://oxicloud.example.com"
|
||||
```
|
||||
|
||||
## Authelia
|
||||
|
||||
Configure an OIDC client in Authelia and allow OxiCloud's redirect URI.
|
||||
|
||||
```yaml
|
||||
identity_providers:
|
||||
oidc:
|
||||
clients:
|
||||
- id: oxicloud
|
||||
description: OxiCloud
|
||||
public: false
|
||||
redirect_uris:
|
||||
- https://oxicloud.example.com/api/auth/oidc/callback
|
||||
scopes: [openid, profile, email, groups]
|
||||
```
|
||||
|
||||
```yaml
|
||||
services:
|
||||
oxicloud:
|
||||
image: diocrafts/oxicloud:latest
|
||||
environment:
|
||||
OXICLOUD_OIDC_PROVIDER_NAME: "Authelia"
|
||||
OXICLOUD_OIDC_ISSUER_URL: "https://authelia.example.com"
|
||||
OXICLOUD_OIDC_CLIENT_ID: "oxicloud"
|
||||
OXICLOUD_OIDC_CLIENT_SECRET: "your-client-secret"
|
||||
```
|
||||
|
||||
## Keycloak
|
||||
|
||||
1. Create a confidential client named `oxicloud`
|
||||
2. Set the valid redirect URI to `https://oxicloud.example.com/api/auth/oidc/callback`
|
||||
3. Copy the generated client secret
|
||||
|
||||
```yaml
|
||||
services:
|
||||
oxicloud:
|
||||
image: diocrafts/oxicloud:latest
|
||||
environment:
|
||||
OXICLOUD_OIDC_PROVIDER_NAME: "Keycloak"
|
||||
OXICLOUD_OIDC_ISSUER_URL: "https://keycloak.example.com/realms/your-realm"
|
||||
OXICLOUD_OIDC_CLIENT_ID: "oxicloud"
|
||||
OXICLOUD_OIDC_CLIENT_SECRET: "your-keycloak-client-secret"
|
||||
OXICLOUD_OIDC_ADMIN_GROUPS: "oxicloud-admins"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Failed to discover the provider
|
||||
|
||||
- Verify the issuer URL exactly matches the provider's discovery URL base
|
||||
- Check DNS, TLS, and network reachability from the OxiCloud container or host
|
||||
|
||||
### Invalid redirect URI
|
||||
|
||||
- Make the configured callback match exactly on scheme, host, port, and path
|
||||
- Check for `http` versus `https` mismatches
|
||||
|
||||
### Auto-provisioning problems
|
||||
|
||||
- Enable `OXICLOUD_OIDC_AUTO_PROVISION=true` if first-login account creation is expected
|
||||
- Make sure `openid` is included in the configured scopes
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [OIDC / SSO](/config/oidc)
|
||||
- [Admin Settings](/config/admin-settings)
|
||||
@@ -10,6 +10,16 @@ OxiCloud supports OpenID Connect for single sign-on with providers like **Keyclo
|
||||
4. IdP redirects back to OxiCloud with an auth code
|
||||
5. OxiCloud exchanges the code for user info and issues its own JWT tokens
|
||||
|
||||
## Architecture
|
||||
|
||||
OIDC follows the Authorization Code Flow and keeps a clear split between provider communication and local session handling.
|
||||
|
||||
- `OidcService` discovers provider metadata, builds authorization URLs, exchanges authorization codes, and validates the token response
|
||||
- `AuthApplicationService` coordinates user lookup or auto-provisioning and then issues OxiCloud's own access and refresh tokens
|
||||
- the auth handler exposes the public OIDC endpoints under `/api/auth/oidc/*`
|
||||
|
||||
After the browser returns from the IdP, OxiCloud does not reuse the provider token for app requests. It converts the identity into its own JWT session model.
|
||||
|
||||
## Configuration
|
||||
|
||||
```bash
|
||||
@@ -54,6 +64,15 @@ If `OXICLOUD_OIDC_ENABLED=true` but `issuer_url`, `client_id`, or `client_secret
|
||||
| GET | `/api/auth/oidc/callback` | Callback from IdP with auth code |
|
||||
| POST | `/api/auth/oidc/exchange` | Exchange auth code for JWT tokens |
|
||||
|
||||
## Identity Mapping
|
||||
|
||||
OIDC users are matched by the pair:
|
||||
|
||||
- `oidc_provider`
|
||||
- `oidc_subject`
|
||||
|
||||
This allows one external identity to map to one local user record and supports just-in-time provisioning when `OXICLOUD_OIDC_AUTO_PROVISION=true`.
|
||||
|
||||
## Provider Examples
|
||||
|
||||
### Keycloak
|
||||
|
||||
+28
-1
@@ -9,6 +9,15 @@ OxiCloud integrates with **Collabora Online** and **OnlyOffice** via the WOPI pr
|
||||
3. The editor fetches the file from OxiCloud via WOPI endpoints
|
||||
4. Edits are saved back via `PutFile`
|
||||
|
||||
## Host / Client Flow
|
||||
|
||||
OxiCloud acts as the **WOPI host** and Collabora or OnlyOffice acts as the **WOPI client**.
|
||||
|
||||
1. OxiCloud loads the discovery document from the configured WOPI client
|
||||
2. The frontend opens a host page that embeds the editor in an iframe
|
||||
3. The iframe URL includes `WOPISrc`, which points back to OxiCloud's `/wopi/files/*` endpoints
|
||||
4. The editor calls back into OxiCloud with the WOPI access token to read, lock, and save the file
|
||||
|
||||
## Configuration
|
||||
|
||||
```bash
|
||||
@@ -68,7 +77,25 @@ services:
|
||||
| GET | `/wopi/files/{id}` | CheckFileInfo — file metadata |
|
||||
| GET | `/wopi/files/{id}/contents` | GetFile — download file content |
|
||||
| POST | `/wopi/files/{id}/contents` | PutFile — save edited content |
|
||||
| POST | `/wopi/files/{id}` | Lock / Unlock / RefreshLock |
|
||||
| POST | `/wopi/files/{id}` | Lock / Unlock / RefreshLock / Rename / Delete / Save As |
|
||||
|
||||
### Common `X-WOPI-Override` operations
|
||||
|
||||
| Override | Purpose |
|
||||
| --- | --- |
|
||||
| `LOCK` | Acquire or refresh an editor lock |
|
||||
| `UNLOCK` | Release a lock |
|
||||
| `REFRESH_LOCK` | Extend the current lock |
|
||||
| `PUT_RELATIVE` | Save as a related file |
|
||||
| `RENAME_FILE` | Rename from inside the editor |
|
||||
| `DELETE` | Delete from the editor when supported |
|
||||
|
||||
## Notes
|
||||
|
||||
- `CheckFileInfo` is required for every WOPI action
|
||||
- `PutFile` uploads the full file content back to OxiCloud
|
||||
- lock conflicts return `409 Conflict` with the current lock value
|
||||
- OxiCloud uses query parameter `?access_token=` authentication for WOPI callbacks instead of the standard JWT middleware
|
||||
|
||||
## Supported Formats
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# Batch Operations
|
||||
|
||||
OxiCloud exposes batch endpoints for bulk file and folder operations under `/api/batch`. Batch requests reduce round-trips, run concurrently behind a semaphore, and return per-item success and failure details instead of aborting on the first error.
|
||||
|
||||
## What You Can Do
|
||||
|
||||
### File operations
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `POST` | `/api/batch/files/move` | Move multiple files into a target folder |
|
||||
| `POST` | `/api/batch/files/copy` | Copy multiple files into a target folder |
|
||||
| `POST` | `/api/batch/files/delete` | Delete multiple files |
|
||||
| `POST` | `/api/batch/files/get` | Fetch metadata for multiple files |
|
||||
|
||||
### Folder operations
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `POST` | `/api/batch/folders/delete` | Delete multiple folders |
|
||||
| `POST` | `/api/batch/folders/create` | Create multiple folders |
|
||||
| `POST` | `/api/batch/folders/get` | Fetch metadata for multiple folders |
|
||||
| `POST` | `/api/batch/folders/move` | Move multiple folders |
|
||||
|
||||
### Additional batch endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `POST` | `/api/batch/trash` | Trash multiple items in one request |
|
||||
| `POST` | `/api/batch/download` | Build a batch download |
|
||||
|
||||
## Request Shapes
|
||||
|
||||
### File move or copy
|
||||
|
||||
```json
|
||||
{
|
||||
"file_ids": ["id-1", "id-2", "id-3"],
|
||||
"target_folder_id": "folder-abc"
|
||||
}
|
||||
```
|
||||
|
||||
### Folder delete
|
||||
|
||||
```json
|
||||
{
|
||||
"folder_ids": ["folder-1", "folder-2"],
|
||||
"recursive": true
|
||||
}
|
||||
```
|
||||
|
||||
### Folder create
|
||||
|
||||
```json
|
||||
{
|
||||
"folders": [
|
||||
{ "name": "Documents", "parent_id": null },
|
||||
{ "name": "Photos", "parent_id": "folder-abc" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
All batch endpoints return the same envelope:
|
||||
|
||||
```json
|
||||
{
|
||||
"successful": [
|
||||
{ "id": "id-1" }
|
||||
],
|
||||
"failed": [
|
||||
{ "id": "bad-id", "error": "File not found" }
|
||||
],
|
||||
"stats": {
|
||||
"total": 5,
|
||||
"successful": 4,
|
||||
"failed": 1,
|
||||
"execution_time_ms": 245
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Status codes
|
||||
|
||||
| Code | Meaning |
|
||||
| --- | --- |
|
||||
| `200 OK` or `201 Created` | Every operation succeeded |
|
||||
| `206 Partial Content` | Some operations succeeded and some failed |
|
||||
| `400 Bad Request` | Every operation failed |
|
||||
|
||||
## Concurrency Model
|
||||
|
||||
Batch work is coordinated by `BatchOperationService` and a `tokio::sync::Semaphore`. By default, OxiCloud caps concurrent work with `max_concurrent_files = 10` so large batches do not starve the rest of the application.
|
||||
|
||||
Individual failures are collected in the `failed` array. One bad item does not cancel the whole request unless the batch cannot start at all.
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
# Move three files into a folder
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"file_ids":["id-1","id-2","id-3"],"target_folder_id":"folder-abc"}' \
|
||||
"https://oxicloud.example.com/api/batch/files/move"
|
||||
|
||||
# Delete multiple folders recursively
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"folder_ids":["old-1","old-2"],"recursive":true}' \
|
||||
"https://oxicloud.example.com/api/batch/folders/delete"
|
||||
```
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [Search](/guide/search)
|
||||
- [Trash & Recycle Bin](/guide/trash)
|
||||
- [ZIP and Compression](/guide/zip-and-compression)
|
||||
@@ -16,6 +16,33 @@ https://your-server:8086/caldav/
|
||||
- RFC 5545 (iCalendar format)
|
||||
- DAV capabilities: `1, 2, calendar-access`
|
||||
|
||||
### Route Structure
|
||||
|
||||
CalDAV is mounted at the top level, not under `/api`:
|
||||
|
||||
- `/caldav`
|
||||
- `/caldav/`
|
||||
- `/caldav/{*path}`
|
||||
|
||||
OxiCloud also exposes `/.well-known/caldav` and redirects it to `/caldav/`.
|
||||
|
||||
Typical resource shapes:
|
||||
|
||||
- `/caldav/` for the calendar home
|
||||
- `/caldav/{calendar_id}/` for one calendar
|
||||
- `/caldav/{calendar_id}/{ical_uid}.ics` for one event
|
||||
|
||||
### Supported Methods
|
||||
|
||||
- `OPTIONS`
|
||||
- `PROPFIND`
|
||||
- `REPORT`
|
||||
- `MKCALENDAR`
|
||||
- `PUT`
|
||||
- `GET`
|
||||
- `DELETE`
|
||||
- `PROPPATCH`
|
||||
|
||||
### Client Setup
|
||||
|
||||
| Client | URL |
|
||||
@@ -48,6 +75,31 @@ https://your-server:8086/carddav/
|
||||
- RFC 6352 (CardDAV)
|
||||
- RFC 6350 (vCard 4.0)
|
||||
|
||||
### Route Structure
|
||||
|
||||
CardDAV is also mounted at the top level:
|
||||
|
||||
- `/carddav`
|
||||
- `/carddav/`
|
||||
- `/carddav/{*path}`
|
||||
|
||||
Typical resource shapes:
|
||||
|
||||
- `/carddav/` for the address book home
|
||||
- `/carddav/{addressBookId}/` for one address book
|
||||
- `/carddav/{addressBookId}/{contactId}.vcf` for one contact
|
||||
|
||||
### Supported Methods
|
||||
|
||||
- `OPTIONS`
|
||||
- `PROPFIND`
|
||||
- `REPORT`
|
||||
- `MKCOL`
|
||||
- `PUT`
|
||||
- `GET`
|
||||
- `DELETE`
|
||||
- `PROPPATCH`
|
||||
|
||||
### Client Setup
|
||||
|
||||
| Client | URL |
|
||||
@@ -68,3 +120,7 @@ https://your-server:8086/carddav/
|
||||
::: info
|
||||
DAVx⁵ file sync works. CalDAV/CardDAV support on DAVx⁵ is still being refined.
|
||||
:::
|
||||
|
||||
## Client Setup
|
||||
|
||||
For platform-specific instructions, see [DAV Client Setup](/guide/dav-client-setup).
|
||||
|
||||
@@ -1,63 +1,107 @@
|
||||
# Chunked Uploads
|
||||
|
||||
OxiCloud supports TUS-like chunked uploads for large files. Uploads are parallel, resumable, and have MD5 integrity checks.
|
||||
OxiCloud exposes resumable chunked uploads under `/api/uploads`. The protocol is TUS-like in spirit, but the concrete API is OxiCloud-specific: create a session, stream chunks with `PATCH`, inspect progress with `HEAD`, then finalize the assembled file.
|
||||
|
||||
## How It Works
|
||||
## Upload Flow
|
||||
|
||||
1. Client sends `POST /api/files/upload/init` with file metadata → receives an `upload_id`
|
||||
2. Client splits the file into chunks and uploads them in parallel via `POST /api/files/upload/chunk`
|
||||
3. Each chunk includes its index, MD5 hash, and the `upload_id`
|
||||
4. When all chunks are uploaded, client calls `POST /api/files/upload/complete`
|
||||
5. Server reassembles the file, verifies integrity, and runs deduplication
|
||||
1. Create an upload session with `POST /api/uploads`
|
||||
2. Upload each chunk with `PATCH /api/uploads/{upload_id}?chunk_index=N`
|
||||
3. Optionally inspect progress with `HEAD /api/uploads/{upload_id}`
|
||||
4. Finalize with `POST /api/uploads/{upload_id}/complete`
|
||||
5. Cancel an in-flight upload with `DELETE /api/uploads/{upload_id}` if needed
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Initialize Upload
|
||||
### Create upload session
|
||||
|
||||
```http
|
||||
POST /api/files/upload/init
|
||||
POST /api/uploads
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"file_name": "large-video.mp4",
|
||||
"filename": "large-video.mp4",
|
||||
"folder_id": "folder-uuid",
|
||||
"content_type": "video/mp4",
|
||||
"total_size": 524288000,
|
||||
"chunk_size": 8388608,
|
||||
"total_chunks": 63
|
||||
"chunk_size": 8388608
|
||||
}
|
||||
```
|
||||
|
||||
### Upload Chunk
|
||||
|
||||
```http
|
||||
POST /api/files/upload/chunk
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
upload_id: "uuid"
|
||||
chunk_index: 0
|
||||
chunk_hash: "md5-hex"
|
||||
file: <binary>
|
||||
```
|
||||
|
||||
### Complete Upload
|
||||
|
||||
```http
|
||||
POST /api/files/upload/complete
|
||||
Content-Type: application/json
|
||||
Typical response:
|
||||
|
||||
```json
|
||||
{
|
||||
"upload_id": "uuid"
|
||||
"upload_id": "uuid",
|
||||
"chunk_size": 8388608,
|
||||
"total_chunks": 63,
|
||||
"expires_at": 86400
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
### Upload a chunk
|
||||
|
||||
| Parameter | Default | Description |
|
||||
|---|---|---|
|
||||
| Max parallel chunks | 8 | Concurrent chunk uploads |
|
||||
| Min size for chunking | 200 MB | Below this, single-shot upload is used |
|
||||
| Chunk size | 8 MB | Default chunk size |
|
||||
Chunks are sent as raw bytes, not multipart form uploads.
|
||||
|
||||
## Frontend Behaviour
|
||||
```http
|
||||
PATCH /api/uploads/{upload_id}?chunk_index=0&checksum=md5-hex
|
||||
Content-Type: application/octet-stream
|
||||
Content-MD5: md5-hex
|
||||
|
||||
The OxiCloud web UI automatically selects chunked upload for large files. A progress bar shows overall completion and current chunk status.
|
||||
<binary chunk bytes>
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `chunk_index` is required and zero-based
|
||||
- `checksum` is optional and can also be supplied with the `Content-MD5` header
|
||||
- Successful responses include progress headers such as `Upload-Offset`, `Upload-Progress`, and `Upload-Complete`
|
||||
|
||||
### Inspect upload status
|
||||
|
||||
```http
|
||||
HEAD /api/uploads/{upload_id}
|
||||
```
|
||||
|
||||
The response includes upload metadata in headers such as:
|
||||
|
||||
- `Upload-Offset`
|
||||
- `Upload-Length`
|
||||
- `Upload-Progress`
|
||||
- `Upload-Chunks-Total`
|
||||
- `Upload-Chunks-Complete`
|
||||
|
||||
### Finalize upload
|
||||
|
||||
```http
|
||||
POST /api/uploads/{upload_id}/complete
|
||||
```
|
||||
|
||||
Successful responses return the created file metadata:
|
||||
|
||||
```json
|
||||
{
|
||||
"file_id": "uuid",
|
||||
"filename": "large-video.mp4",
|
||||
"size": 524288000,
|
||||
"path": "/Videos/large-video.mp4"
|
||||
}
|
||||
```
|
||||
|
||||
### Cancel upload
|
||||
|
||||
```http
|
||||
DELETE /api/uploads/{upload_id}
|
||||
```
|
||||
|
||||
This removes the in-progress session and temporary chunk data.
|
||||
|
||||
## Validation Rules
|
||||
|
||||
- `filename` is required
|
||||
- `total_size` must be greater than zero
|
||||
- `chunk_size` must be at least 1 MB when provided
|
||||
- Storage quota checks can reject the session before upload starts
|
||||
|
||||
## Frontend Behavior
|
||||
|
||||
The OxiCloud web UI can switch to chunked uploads for larger files, track aggregate progress, and retry individual chunks without restarting the full transfer.
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# DAV Client Setup
|
||||
|
||||
This page collects platform-specific connection steps for OxiCloud's WebDAV, CalDAV, and CardDAV endpoints.
|
||||
|
||||
## Connection Summary
|
||||
|
||||
| Use case | URL |
|
||||
| --- | --- |
|
||||
| WebDAV file access | `https://your-oxicloud-server/webdav/` |
|
||||
| CalDAV calendar sync | `https://your-oxicloud-server/caldav` |
|
||||
| CardDAV contact sync | `https://your-oxicloud-server/carddav` |
|
||||
|
||||
## WebDAV
|
||||
|
||||
### Windows Explorer
|
||||
|
||||
1. Open File Explorer
|
||||
2. Right-click This PC and choose Add a network location or Map network drive
|
||||
3. Enter `https://your-oxicloud-server/webdav/`
|
||||
4. Provide your OxiCloud username and password
|
||||
|
||||
If Windows refuses the connection, check the `WebClient` service and verify these registry values under `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters`:
|
||||
|
||||
- `BasicAuthLevel = 2` when Basic auth is required
|
||||
- `FileSizeLimitInBytes` if you need to allow larger transfers
|
||||
|
||||
### macOS Finder
|
||||
|
||||
1. Open Finder
|
||||
2. Choose Go -> Connect to Server or press Cmd+K
|
||||
3. Enter `https://your-oxicloud-server/webdav/`
|
||||
4. Sign in with your OxiCloud credentials
|
||||
|
||||
### Linux
|
||||
|
||||
- GNOME Files: use `davs://your-oxicloud-server/webdav/`
|
||||
- KDE Dolphin: use `webdavs://your-oxicloud-server/webdav/`
|
||||
- `davfs2`: mount `https://your-oxicloud-server/webdav/` to a local directory
|
||||
|
||||
## CalDAV
|
||||
|
||||
### Apple Calendar
|
||||
|
||||
Use an advanced CalDAV account and point it at `https://your-oxicloud-server/caldav`.
|
||||
|
||||
### Thunderbird
|
||||
|
||||
Create a network calendar and use a CalDAV location such as:
|
||||
|
||||
```text
|
||||
https://your-oxicloud-server/caldav/calendars/your-calendar-id
|
||||
```
|
||||
|
||||
### Android with DAVx5
|
||||
|
||||
Use Login with URL and username, then point the base URL at `https://your-oxicloud-server/caldav`.
|
||||
|
||||
### Outlook on Windows
|
||||
|
||||
Use a CalDAV plugin such as CalDAV Synchronizer and register the calendar endpoint explicitly.
|
||||
|
||||
## CardDAV
|
||||
|
||||
### Apple Contacts
|
||||
|
||||
Create a CardDAV account using `https://your-oxicloud-server/carddav`.
|
||||
|
||||
### Thunderbird
|
||||
|
||||
Use a remote address book with a URL such as:
|
||||
|
||||
```text
|
||||
https://your-oxicloud-server/carddav/address-books/your-address-book-id
|
||||
```
|
||||
|
||||
### Android with DAVx5
|
||||
|
||||
Use the CardDAV base URL `https://your-oxicloud-server/carddav`.
|
||||
|
||||
### Outlook on Windows
|
||||
|
||||
Use a CardDAV-capable synchronizer and configure the remote address book endpoint explicitly.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### WebDAV
|
||||
|
||||
- Make sure the URL includes `/webdav/`
|
||||
- Use HTTPS in production
|
||||
- Recheck credentials and the WebClient service on Windows
|
||||
|
||||
### CalDAV and CardDAV
|
||||
|
||||
- Use the full `/caldav` or `/carddav` base path
|
||||
- Verify the calendar or address book identifier when the client asks for one
|
||||
- If sync works on one client and not another, compare the exact URLs being used
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [WebDAV](/guide/webdav)
|
||||
- [CalDAV & CardDAV](/guide/caldav-carddav)
|
||||
+50
-31
@@ -1,46 +1,65 @@
|
||||
# File Deduplication
|
||||
|
||||
OxiCloud uses **SHA-256 content-addressable storage** to avoid storing duplicate files. If two users upload the same file, only one copy is stored on disk.
|
||||
OxiCloud uses **content-defined chunking (CDC)** with **FastCDC** and **BLAKE3** to deduplicate files at the sub-file level. Instead of storing only whole-file blobs, OxiCloud can split a file into variable-size chunks, reuse unchanged chunks across versions, and track the ordered chunk list in PostgreSQL.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. When a file is uploaded, its SHA-256 hash is computed
|
||||
2. The hash is checked against the blob store (`.blobs/{prefix}/{hash}.blob`)
|
||||
3. If a blob with that hash already exists, the file metadata points to the existing blob (no extra disk usage)
|
||||
4. If not, the content is saved as a new blob
|
||||
5. A reference counter tracks how many files point to each blob
|
||||
1. OxiCloud analyzes the uploaded file with FastCDC
|
||||
2. The file is split into variable-size chunks from **64 KB** to **1 MB**, targeting an average of **256 KB**
|
||||
3. Each chunk is hashed with **BLAKE3** and checked against the blob index
|
||||
4. Only new chunks are written to the blob backend
|
||||
5. A manifest in PostgreSQL maps the whole-file hash to the ordered chunk hash list
|
||||
6. Reference counts are updated so identical chunks are stored once even across multiple files or edited versions
|
||||
|
||||
## Automatic Cleanup
|
||||
## Storage Model
|
||||
|
||||
```text
|
||||
storage.files -> metadata rows that reference content
|
||||
chunk_manifests -> file_hash -> [chunk_hashes] + chunk_sizes + ref_count
|
||||
storage.blobs -> per-chunk blob metadata and reference counts
|
||||
blob backend -> actual chunk bytes on disk or remote storage
|
||||
```
|
||||
|
||||
The manifest table is created in `migrations/20260414000000_chunk_manifests.sql` and keeps:
|
||||
|
||||
- `file_hash`
|
||||
- ordered `chunk_hashes`
|
||||
- `chunk_sizes`
|
||||
- `total_size`
|
||||
- `chunk_count`
|
||||
- `ref_count`
|
||||
|
||||
## Why CDC Matters
|
||||
|
||||
Whole-file dedup only helps when two files are byte-for-byte identical. CDC helps when files are similar but not identical, for example:
|
||||
|
||||
- edited office documents
|
||||
- versioned project archives
|
||||
- large media files with partial changes
|
||||
|
||||
In those cases, unchanged chunks can be reused and only the modified portions need new storage.
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
Older uploads stored before CDC are still readable. When OxiCloud does not find a matching manifest row, it falls back to legacy whole-file blob reads.
|
||||
|
||||
## Cleanup Behavior
|
||||
|
||||
When a file is permanently deleted:
|
||||
|
||||
1. The blob's reference count is decremented
|
||||
2. If the reference count reaches zero, the blob is removed from disk
|
||||
1. OxiCloud decrements the manifest reference count
|
||||
2. If the last manifest reference disappears, chunk refcounts are decremented
|
||||
3. Chunks with `ref_count = 0` are removed from the blob index and then deleted from the backend
|
||||
|
||||
This means disk space is only freed when the **last** reference to a blob is removed.
|
||||
|
||||
## Storage Layout
|
||||
|
||||
```
|
||||
storage/
|
||||
├── .blobs/
|
||||
│ ├── a1/
|
||||
│ │ └── a1b2c3d4...sha256.blob
|
||||
│ ├── f8/
|
||||
│ │ └── f8e7d6c5...sha256.blob
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
The first two hex characters of the hash are used as a directory prefix to avoid having millions of files in a single directory.
|
||||
This keeps storage correct even when multiple files share the same chunk set.
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Disk savings** — identical files across users consume storage only once
|
||||
- **Instant uploads** — if the blob already exists, the upload completes immediately
|
||||
- **Integrity** — SHA-256 ensures bit-for-bit correctness
|
||||
- Better storage savings for edited and versioned files
|
||||
- Faster repeat uploads when many chunks already exist
|
||||
- BLAKE3 hashing for fast content verification
|
||||
- PostgreSQL-backed manifests for durable indexing and cleanup
|
||||
|
||||
## Limitations
|
||||
## Related Endpoints
|
||||
|
||||
- Deduplication is based on exact content match (byte-identical files)
|
||||
- Near-duplicate files (e.g., a JPEG re-saved at slightly different quality) are stored separately
|
||||
- Encryption at rest would require per-user keys, which breaks deduplication (planned as opt-in)
|
||||
The dedup subsystem is also exposed through helper endpoints under `/api/dedup` for hash checks, deduplicated uploads, statistics, and maintenance operations.
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# Favorites and Recent Items
|
||||
|
||||
OxiCloud includes two per-user tracking features backed by PostgreSQL:
|
||||
|
||||
- Favorites for pinning files and folders you want to reach quickly
|
||||
- Recent items for tracking the files and folders you accessed most recently
|
||||
|
||||
Both features are enabled when the instance has a database connection.
|
||||
|
||||
## Favorites
|
||||
|
||||
### API
|
||||
|
||||
All routes live under `/api/favorites` and require authentication.
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/favorites/` | List all favorites for the current user |
|
||||
| `POST` | `/api/favorites/{item_type}/{item_id}` | Add a file or folder to favorites |
|
||||
| `DELETE` | `/api/favorites/{item_type}/{item_id}` | Remove a favorite |
|
||||
|
||||
`item_type` must be either `file` or `folder`.
|
||||
|
||||
### Behavior
|
||||
|
||||
- Adding the same item twice is idempotent
|
||||
- Results are ordered by `created_at DESC`
|
||||
- User identity comes from the JWT, not the request body
|
||||
|
||||
### Storage model
|
||||
|
||||
Favorites are stored in `auth.user_favorites` with a uniqueness constraint on `(user_id, item_id, item_type)`.
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS auth.user_favorites (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
item_id TEXT NOT NULL,
|
||||
item_type TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, item_id, item_type)
|
||||
);
|
||||
```
|
||||
|
||||
## Recent Items
|
||||
|
||||
### API
|
||||
|
||||
All routes live under `/api/recent` and require authentication.
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/recent/` | List recent items, optionally with `?limit=N` |
|
||||
| `POST` | `/api/recent/{item_type}/{item_id}` | Record an access |
|
||||
| `DELETE` | `/api/recent/{item_type}/{item_id}` | Remove one item from history |
|
||||
| `DELETE` | `/api/recent/clear` | Clear all recent items |
|
||||
|
||||
### Behavior
|
||||
|
||||
- Default maximum per user: 50 items
|
||||
- Re-accessing an item updates its `accessed_at` timestamp
|
||||
- Old items are automatically pruned after inserts
|
||||
- Results are ordered by `accessed_at DESC`
|
||||
|
||||
### Storage model
|
||||
|
||||
Recent items are stored in `auth.user_recent_files`.
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS auth.user_recent_files (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
item_id TEXT NOT NULL,
|
||||
item_type TEXT NOT NULL,
|
||||
accessed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, item_id, item_type)
|
||||
);
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
# Add a file to favorites
|
||||
curl -X POST -H "Authorization: Bearer $TOKEN" \
|
||||
"https://oxicloud.example.com/api/favorites/file/abc-123"
|
||||
|
||||
# List recent items
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://oxicloud.example.com/api/recent/?limit=10"
|
||||
|
||||
# Clear recent history
|
||||
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
|
||||
"https://oxicloud.example.com/api/recent/clear"
|
||||
```
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [Search](/guide/search)
|
||||
- [Trash & Recycle Bin](/guide/trash)
|
||||
- [Batch Operations](/guide/batch-operations)
|
||||
@@ -0,0 +1,65 @@
|
||||
# Internationalization
|
||||
|
||||
OxiCloud exposes a public translation API backed by JSON locale files on disk. Locales are loaded lazily, cached in memory, and served without authentication.
|
||||
|
||||
## Public API
|
||||
|
||||
All routes live under `/api/i18n`.
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/i18n/locales` | List available locales |
|
||||
| `GET` | `/api/i18n/translate?key=...&locale=...` | Resolve a single key |
|
||||
| `GET` | `/api/i18n/locales/{locale_code}` | Fetch all translations for one locale |
|
||||
|
||||
## Locale Files
|
||||
|
||||
Translations are stored as nested JSON files under `static/locales/`.
|
||||
|
||||
Example shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"app": {
|
||||
"title": "OxiCloud"
|
||||
},
|
||||
"nav": {
|
||||
"files": "Files",
|
||||
"trash": "Trash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Keys are resolved with dot notation, so `nav.files` maps to `Files`.
|
||||
|
||||
## Fallback Rules
|
||||
|
||||
- If a key is missing in the requested locale, OxiCloud falls back to English
|
||||
- If the key is missing there as well, the API returns a not-found error for that key
|
||||
|
||||
## Caching Model
|
||||
|
||||
Translations are cached in memory with an `RwLock<HashMap<Locale, serde_json::Value>>` and loaded on first use for each locale.
|
||||
|
||||
## Frontend Usage
|
||||
|
||||
Typical frontend flow:
|
||||
|
||||
1. Detect the preferred locale
|
||||
2. Request `/api/i18n/locales/{code}`
|
||||
3. Apply translated strings to UI elements
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
# List locales
|
||||
curl "https://oxicloud.example.com/api/i18n/locales"
|
||||
|
||||
# Fetch one translation
|
||||
curl "https://oxicloud.example.com/api/i18n/translate?key=app.title&locale=es"
|
||||
```
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [Internal Architecture](/architecture/)
|
||||
- [Environment Variables](/config/env)
|
||||
+27
-16
@@ -3,8 +3,8 @@
|
||||
## Docker (recommended)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/DioCrafts/oxicloud.git
|
||||
cd oxicloud
|
||||
git clone https://github.com/DioCrafts/OxiCloud.git
|
||||
cd OxiCloud
|
||||
cp example.env .env
|
||||
docker compose up -d
|
||||
```
|
||||
@@ -16,14 +16,15 @@ Open **http://localhost:8086**. That's it.
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17.4-alpine
|
||||
image: postgres:18.2-alpine3.23
|
||||
environment:
|
||||
POSTGRES_DB: oxicloud
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- pg_data:/var/lib/postgresql/data
|
||||
- ./db/schema.sql:/docker-entrypoint-initdb.d/schema.sql
|
||||
- pg_data:/var/lib/postgresql/
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
@@ -32,6 +33,9 @@ services:
|
||||
|
||||
oxicloud:
|
||||
image: diocrafts/oxicloud:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8086:8086"
|
||||
env_file:
|
||||
@@ -52,13 +56,19 @@ volumes:
|
||||
Requires **Rust 1.93+** and **PostgreSQL 13+**.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/DioCrafts/oxicloud.git
|
||||
cd oxicloud
|
||||
echo "DATABASE_URL=postgres://user:pass@localhost/oxicloud" > .env
|
||||
git clone https://github.com/DioCrafts/OxiCloud.git
|
||||
cd OxiCloud
|
||||
cp example.env .env
|
||||
|
||||
# Edit .env and set OXICLOUD_DB_CONNECTION_STRING for runtime
|
||||
export DATABASE_URL=postgres://user:pass@localhost:5432/oxicloud
|
||||
|
||||
cargo build --release
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
`OXICLOUD_DB_CONNECTION_STRING` is the runtime setting read by OxiCloud. `DATABASE_URL` is only needed for SQLx build-time checks.
|
||||
|
||||
## Kubernetes (Helm)
|
||||
|
||||
```bash
|
||||
@@ -77,14 +87,14 @@ kubectl logs statefulset/oxicloud -n oxicloud
|
||||
|
||||
| Client | Protocol | URL |
|
||||
|--------|----------|-----|
|
||||
| Windows Explorer | WebDAV | `http://host:8086/webdav/` |
|
||||
| macOS Finder | WebDAV | `http://host:8086/webdav/` |
|
||||
| Nautilus / Dolphin | WebDAV | `dav://host:8086/webdav/` |
|
||||
| Thunderbird (calendar) | CalDAV | `http://host:8086/caldav/` |
|
||||
| Thunderbird (contacts) | CardDAV | `http://host:8086/carddav/` |
|
||||
| DAVx⁵ (Android) | CalDAV + CardDAV | `http://host:8086/` |
|
||||
| GNOME Calendar | CalDAV | `http://host:8086/caldav/` |
|
||||
| GNOME Contacts | CardDAV | `http://host:8086/carddav/` |
|
||||
| Windows Explorer | WebDAV | `https://host/webdav/` |
|
||||
| macOS Finder | WebDAV | `https://host/webdav/` |
|
||||
| Nautilus / Dolphin | WebDAV | `davs://host/webdav/` |
|
||||
| Thunderbird (calendar) | CalDAV | `https://host/caldav/` |
|
||||
| Thunderbird (contacts) | CardDAV | `https://host/carddav/` |
|
||||
| DAVx⁵ (Android) | CalDAV + CardDAV | `https://host/` |
|
||||
| GNOME Calendar | CalDAV | `https://host/caldav/` |
|
||||
| GNOME Contacts | CardDAV | `https://host/carddav/` |
|
||||
| Collabora / OnlyOffice | WOPI | See [WOPI configuration](/config/wopi) |
|
||||
|
||||
## What's Next?
|
||||
@@ -92,3 +102,4 @@ kubectl logs statefulset/oxicloud -n oxicloud
|
||||
- [Environment Variables →](/config/env)
|
||||
- [OIDC / SSO Setup →](/config/oidc)
|
||||
- [WebDAV Guide →](/guide/webdav)
|
||||
- [DAV Client Setup →](/guide/dav-client-setup)
|
||||
|
||||
+50
-27
@@ -1,43 +1,66 @@
|
||||
# Search
|
||||
|
||||
OxiCloud provides full-text search across your files with multiple filter options.
|
||||
OxiCloud provides authenticated file and folder search with simple query parameters, advanced JSON criteria, pagination, recursive traversal, and in-memory result caching.
|
||||
|
||||
## Endpoint
|
||||
## Endpoints
|
||||
|
||||
```http
|
||||
GET /api/search?q=report&type_filter=pdf,docx&recursive=true
|
||||
```
|
||||
| Method | Endpoint | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/search/` | Simple search using query parameters |
|
||||
| `POST` | `/api/search/advanced` | Advanced search with a JSON body |
|
||||
| `GET` | `/api/search/suggest` | Lightweight autocomplete suggestions |
|
||||
| `DELETE` | `/api/search/cache` | Clear the search results cache |
|
||||
|
||||
## Query Parameters
|
||||
All search endpoints require authentication.
|
||||
|
||||
## Simple Search Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
|---|---|
|
||||
| `q` | Search query (matches file name) |
|
||||
| `type_filter` | Comma-separated file extensions to filter by |
|
||||
| `folder_id` | Restrict search to a specific folder |
|
||||
| `recursive` | `true` to search subfolders |
|
||||
| `date_from` / `date_to` | Filter by modification date |
|
||||
| `size_min` / `size_max` | Filter by file size (bytes) |
|
||||
| `limit` | Maximum results to return |
|
||||
| --- | --- |
|
||||
| `query` | Text to search in file and folder names |
|
||||
| `type` | Comma-separated file extensions |
|
||||
| `created_after` / `created_before` | Filter by creation time |
|
||||
| `modified_after` / `modified_before` | Filter by modification time |
|
||||
| `min_size` / `max_size` | Filter by file size in bytes |
|
||||
| `folder_id` | Restrict search scope to one folder |
|
||||
| `recursive` | Search subfolders, defaults to `true` |
|
||||
| `limit` | Maximum results, defaults to `100` |
|
||||
| `offset` | Pagination offset |
|
||||
| `sort_by` | `relevance`, `name`, `name_desc`, `date`, `date_desc`, `size`, or `size_desc` |
|
||||
|
||||
## How It Works
|
||||
### Example
|
||||
|
||||
OxiCloud stores file metadata in PostgreSQL with an `ltree` path column, enabling efficient recursive subtree queries:
|
||||
|
||||
```sql
|
||||
SELECT * FROM storage.files
|
||||
WHERE path <@ 'root.folder_id'
|
||||
AND LOWER(name) LIKE '%query%'
|
||||
AND LOWER(extension) = ANY('{pdf,docx}')
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 50;
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://oxicloud.example.com/api/search/?query=report&type=pdf,docx&recursive=true&limit=20"
|
||||
```
|
||||
|
||||
## Frontend
|
||||
## Advanced Search
|
||||
|
||||
The web UI includes a search bar in the toolbar. Results appear instantly with file name, path, size, and type. Clicking a result navigates to the file's location.
|
||||
```json
|
||||
{
|
||||
"name_contains": "report",
|
||||
"file_types": ["pdf", "docx"],
|
||||
"min_size": 1024,
|
||||
"folder_id": "folder-uuid",
|
||||
"recursive": true,
|
||||
"limit": 50,
|
||||
"offset": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Suggestions
|
||||
|
||||
Use `/api/search/suggest?query=rep&limit=10` for quick autocomplete-style results. Suggestions can also be scoped to a folder with `folder_id`.
|
||||
|
||||
## Result Caching
|
||||
|
||||
Search results are cached in memory using the search criteria and user ID as the cache key.
|
||||
|
||||
- Cache TTL: 5 minutes
|
||||
- Max entries: 1000
|
||||
- Manual invalidation: `DELETE /api/search/cache`
|
||||
|
||||
## Feature Flag
|
||||
|
||||
Search can be disabled via `OXICLOUD_ENABLE_SEARCH=false`.
|
||||
Search can be disabled with `OXICLOUD_ENABLE_SEARCH=false`.
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# Thumbnails and Transcoding
|
||||
|
||||
OxiCloud optimizes image delivery with two complementary features:
|
||||
|
||||
- WebP thumbnail generation in three sizes
|
||||
- On-the-fly image transcoding for browsers that advertise WebP support
|
||||
|
||||
Both features use a memory cache plus a persistent disk cache and are designed to stay off the request hot path whenever possible.
|
||||
|
||||
## Thumbnails
|
||||
|
||||
### Supported sizes
|
||||
|
||||
| Size | Dimensions | Directory |
|
||||
| --- | --- | --- |
|
||||
| `icon` | 150 x 150 | `.thumbnails/icon/` |
|
||||
| `preview` | 400 x 400 | `.thumbnails/preview/` |
|
||||
| `large` | 800 x 800 | `.thumbnails/large/` |
|
||||
|
||||
### Supported inputs
|
||||
|
||||
`image/jpeg`, `image/jpg`, `image/png`, `image/gif`, and `image/webp`
|
||||
|
||||
All thumbnail outputs are stored as WebP.
|
||||
|
||||
### API
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/files/{id}/thumbnail/{size}` | Fetch a thumbnail |
|
||||
| `POST` | `/api/files/upload` | Upload a file and pre-generate thumbnails for supported images |
|
||||
|
||||
Thumbnail responses include:
|
||||
|
||||
- `Content-Type: image/webp`
|
||||
- `Cache-Control: public, max-age=31536000, immutable`
|
||||
- `ETag: "thumb-{id}-{size}"`
|
||||
|
||||
### Generation flow
|
||||
|
||||
1. Upload succeeds through the file API
|
||||
2. If the MIME type is supported, OxiCloud starts thumbnail generation in a background task
|
||||
3. If a thumbnail is requested before pre-generation completes, the request can generate it lazily
|
||||
4. Future requests are served from memory or disk cache
|
||||
|
||||
## Image Transcoding
|
||||
|
||||
OxiCloud can serve a smaller WebP version of uploaded JPEG, PNG, or GIF files when the client advertises WebP support in the `Accept` header.
|
||||
|
||||
### Rules
|
||||
|
||||
- Files over 5 MB skip transcoding
|
||||
- Existing WebP files are not transcoded again
|
||||
- SVG and BMP are not transcoded
|
||||
- If the WebP output is larger than the original, OxiCloud serves the original file instead
|
||||
|
||||
### Storage layout
|
||||
|
||||
```text
|
||||
<storage_path>/
|
||||
.transcoded/
|
||||
webp/
|
||||
<file_id>.webp
|
||||
```
|
||||
|
||||
### Statistics tracked by the service
|
||||
|
||||
- Cache hits
|
||||
- Disk hits
|
||||
- Successful transcodes
|
||||
- Bytes saved
|
||||
- Transcode errors
|
||||
|
||||
## Caching
|
||||
|
||||
Both thumbnail and transcode services use:
|
||||
|
||||
- An in-memory LRU cache for hot assets
|
||||
- A disk cache for persistent reuse across restarts
|
||||
- Fire-and-forget background writes for cache warmup
|
||||
|
||||
For the broader cache model across metadata and listings, see [Caching Architecture](/architecture/caching).
|
||||
|
||||
## Example
|
||||
|
||||
```text
|
||||
Client: GET /api/files/abc-123/download
|
||||
Accept: image/webp, image/png, */*
|
||||
|
||||
Server: checks cache -> transcodes if needed -> returns the smaller asset
|
||||
```
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [Caching Architecture](/architecture/caching)
|
||||
- [ZIP and Compression](/guide/zip-and-compression)
|
||||
+12
-1
@@ -9,12 +9,21 @@ OxiCloud provides a trash system that soft-deletes files and folders, allowing u
|
||||
3. Users can browse the trash, restore items, or permanently delete them
|
||||
4. Items older than the retention period (default: **30 days**) are automatically purged
|
||||
|
||||
## Storage Model
|
||||
|
||||
- files and folders keep their original rows in PostgreSQL
|
||||
- deletion into trash only flips soft-delete state and records the original parent location for restore
|
||||
- blob content is not moved when an item enters the trash
|
||||
- the unified `storage.trash_items` view is used to list trashed files and folders together
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/trash` | List trashed items |
|
||||
| POST | `/api/trash/restore/{id}` | Restore a trashed item |
|
||||
| DELETE | `/api/trash/files/{id}` | Move a file to the trash |
|
||||
| DELETE | `/api/trash/folders/{id}` | Move a folder to the trash |
|
||||
| POST | `/api/trash/{id}/restore` | Restore a trashed item |
|
||||
| DELETE | `/api/trash/{id}` | Permanently delete |
|
||||
| DELETE | `/api/trash/empty` | Empty the entire trash |
|
||||
|
||||
@@ -25,3 +34,5 @@ Permanent deletion decrements the blob reference count. If no other file points
|
||||
## Feature Flag
|
||||
|
||||
Trash can be disabled via `OXICLOUD_ENABLE_TRASH=false`. When disabled, deletions are permanent.
|
||||
|
||||
Retention is controlled by `OXICLOUD_TRASH_RETENTION_DAYS`.
|
||||
|
||||
@@ -33,6 +33,65 @@ Always use HTTPS in production — Basic auth sends credentials in every request
|
||||
| `DELETE` | Delete a file/folder |
|
||||
| `LOCK` / `UNLOCK` | File locking |
|
||||
|
||||
## Common Operations
|
||||
|
||||
### List a directory
|
||||
|
||||
Use `PROPFIND` with a `Depth` header:
|
||||
|
||||
```http
|
||||
PROPFIND /webdav/projects/ HTTP/1.1
|
||||
Depth: 1
|
||||
Content-Type: application/xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:propfind xmlns:D="DAV:">
|
||||
<D:allprop/>
|
||||
</D:propfind>
|
||||
```
|
||||
|
||||
Successful directory listings return `207 Multi-Status`.
|
||||
|
||||
### Download a file
|
||||
|
||||
```http
|
||||
GET /webdav/projects/document.pdf HTTP/1.1
|
||||
Authorization: Basic base64(username:password)
|
||||
```
|
||||
|
||||
### Upload or replace a file
|
||||
|
||||
```http
|
||||
PUT /webdav/projects/document.pdf HTTP/1.1
|
||||
Content-Type: application/pdf
|
||||
|
||||
<file bytes>
|
||||
```
|
||||
|
||||
### Create a folder
|
||||
|
||||
```http
|
||||
MKCOL /webdav/projects/new-folder HTTP/1.1
|
||||
```
|
||||
|
||||
### Move or copy
|
||||
|
||||
```http
|
||||
MOVE /webdav/old-location.pdf HTTP/1.1
|
||||
Destination: https://your-server/webdav/new-location.pdf
|
||||
```
|
||||
|
||||
```http
|
||||
COPY /webdav/original.pdf HTTP/1.1
|
||||
Destination: https://your-server/webdav/copy.pdf
|
||||
```
|
||||
|
||||
### Delete a resource
|
||||
|
||||
```http
|
||||
DELETE /webdav/projects/document.pdf HTTP/1.1
|
||||
```
|
||||
|
||||
## Client Setup
|
||||
|
||||
### Windows Explorer
|
||||
@@ -78,3 +137,16 @@ curl -u user:pass -X MKCOL https://your-server:8086/webdav/new-folder/
|
||||
## Streaming PROPFIND
|
||||
|
||||
OxiCloud streams PROPFIND responses, so listing directories with thousands of files doesn't consume excessive memory.
|
||||
|
||||
## Integration Notes
|
||||
|
||||
- the WebDAV handler is only an HTTP adapter; file and folder operations still go through the same application services used by the REST API
|
||||
- HTTP Basic Authentication is supported for DAV clients, while authorization rules remain the same as the rest of OxiCloud
|
||||
- delete operations integrate with trash when the trash feature is enabled
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Always use the `/webdav/` base path
|
||||
- Prefer HTTPS because WebDAV uses Basic Authentication
|
||||
- On Windows, make sure the `WebClient` service is enabled
|
||||
- OxiCloud rejects path traversal segments such as `.` and `..` at the HTTP boundary
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# ZIP and Compression
|
||||
|
||||
OxiCloud ships two compression-related features:
|
||||
|
||||
- ZIP download for folders
|
||||
- Gzip compression for suitable file responses
|
||||
|
||||
## ZIP Download
|
||||
|
||||
### Endpoint
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/folders/{id}/download` | Download a folder as a ZIP archive |
|
||||
|
||||
### How it works
|
||||
|
||||
- The ZIP archive is built in memory
|
||||
- Folder traversal uses an iterative queue rather than recursive async calls
|
||||
- Cycle detection prevents loops while walking nested folders
|
||||
- Entries are written with UNIX mode `0o755`
|
||||
- Compression uses the `Deflated` method from the `zip` crate
|
||||
|
||||
### Example
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
"https://oxicloud.example.com/api/folders/abc-123/download" \
|
||||
-o my-folder.zip
|
||||
```
|
||||
|
||||
## Gzip Compression
|
||||
|
||||
OxiCloud can compress responses when it is worth doing so.
|
||||
|
||||
### Compression threshold
|
||||
|
||||
Files below 50 KB are skipped.
|
||||
|
||||
### Compression levels
|
||||
|
||||
| Level | Value |
|
||||
| --- | --- |
|
||||
| `None` | `0` |
|
||||
| `Fast` | `1` |
|
||||
| `Default` | `6` |
|
||||
| `Best` | `9` |
|
||||
|
||||
### Skip list
|
||||
|
||||
These types are not gzipped because they are already compressed or because compression provides poor returns:
|
||||
|
||||
- `image/*` except SVG and BMP
|
||||
- `audio/*`
|
||||
- `video/*`
|
||||
- archive formats such as ZIP, gzip, 7z, RAR, bzip2, and XZ
|
||||
|
||||
### Runtime behavior
|
||||
|
||||
- Compression and decompression run in `spawn_blocking`
|
||||
- The implementation uses `flate2`
|
||||
- Optional buffer pooling reduces allocation churn under load
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [Batch Operations](/guide/batch-operations)
|
||||
- [Thumbnails and Transcoding](/guide/thumbnails-and-transcoding)
|
||||
Reference in New Issue
Block a user