32 KiB
Cloudreve Fork — Analysis & Roadmap
Fork: Dvorinka/cloudreve · Upstream: cloudreve/cloudreve · Baseline: 4.19.1 (exact upstream HEAD, zero divergence)
Original work by the Cloudreve authors (cloudreve.org). This fork continues it as a fully open-source project — every "Pro" feature reimplemented and free, desktop client on all platforms, native Android app.
1. Analysis
1.1 Repo state
| Fact | Value |
|---|---|
| Fork vs upstream | 0 ahead / 0 behind — clean mirror of master @ 4.19.1 |
| Backend | Go 1.26, Gin, ent ORM, go build ./... compiles (only assets.zip embed fails until frontend is built — expected) |
| Frontend | assets/ submodule → cloudreve/frontend (React + Vite + TS, 69 deps), not yet initialized locally |
| Storage drivers present | local, S3, OSS, COS, Qiniu, Upyun, OneDrive, remote node (service/explorer/slave.go) |
| Auth present | password+2FA, passkey (ent/schema/passkey.go), generic OAuth client/grant, WebDAV accounts (davaccount.go) |
1.2 Security posture
16 published GHSAs on upstream — all fixed at our baseline (vuln ranges ≤ 4.17.0, we run 4.19.1). Spot-verified in tree, not just by version:
- GHSA-f8xp (account takeover, insecure PRNG):
pkg/util/common.gousescrypto/randprimary,math/randonly on crypto failure — fix present - GHSA-vgj4 (OAuth scope bypass, missing client_id):
service/oauth/oauth.go:159validatesauthCode.ClientID != s.ClientID— fix present - Remaining highs (WebDAV path traversal, quota TOCTOU, OneDrive cred update via Admin.Read) — all ≤ 4.16.x ranges, patched
Ongoing security work is in the roadmap (§5), not the backlog.
1.3 Pro feature map (cloudreve.org/pricing — all 5 slides captured)
The community repo contains zero Pro code — Pro ships as a separate licensed binary (--license-key, proupgrade DB migration). BUT the community frontend already contains the full Pro UI skeleton — every surface below renders a ProChip badge that opens ProDialog.tsx. Implementation = backend endpoints + remove the chips.
| Pro slide | Features | Frontend hooks found |
|---|---|---|
| Sharing & collaboration | write/upload/delete via share link, paid share links, granular file permissions (users/groups/anonymous), anonymous upload via share, default shares for new users | ShareSection.tsx (group editor) |
| Storage policy mgmt | multiple policies per group, per-directory policies, load-balancer policy, file migration between policies | SelectProvider.tsx, storage_policy_id exists (single) on group |
| User & auth | multi-account switching, Logto SSO, OIDC SSO, QQ Connect, sign-up email filtering | SSOSettings.tsx, SSO/ |
| Monetization (VAS) | storage plans, membership plans, redemption codes, credits | VAS/ dir: GroupProducts, StorageProducts, PaymentProviders, GiftCodes — full UI exists |
| System extensions | activity/audit logs, site announcements, node selection, report abuse | Events.tsx (admin), Home.tsx |
Backend gaps are concrete: ShareProps = {share_view, show_read_me} only; group.storage_policy_id is single; no order/product/credit entities at all. NavigatorCapability_CommunityPlaceholder1–9 in pkg/filemanager/fs/dbfs/navigator.go are the reserved capability slots Pro fills.
1.3a Pro UI reference — extracted from cloudreve.org/imgs/features/en_*.png
All 22 reference shots vendored to output/playwright/pro-features/. What each actually shows (implementation-level detail, not marketing text):
Sharing & collaboration
| Feature | What the UI reveals | Implementation shape |
|---|---|---|
| Modify/delete via share | "Create share link" dialog embeds the ACL picker itself | Share entity carries a capability set; already landed (#140) |
| File permissions | Context menu → More actions → Permissions: "Explicit access" list (users by email, groups like "Same group with me") each with a dot-joined capability dropdown + ×; "General access" rows: Anonymous visitors, Everyone else; search box "Search for emails or groups…" | ACL entity: `(file_id |
| Paid share links | "Pay to download" section in share dialog: points price field + "Gain N points to you per purchasing" hint (take-rate visible: 50 → 40, i.e. ~80% to sharer). Buyer flow: gate page "You need to pay N Points" → dialog: Pay with points (login req.) / Pay with cash (¥0.50 equiv ⇒ 1 pt = ¥0.01) / Restore purchase via "Resume ticket" from order email | share.price_points; purchase record + resume-ticket token; credits ledger (B.4) funds both sides; take-rate = admin setting |
| Anonymous upload | Same ACL dialog — anonymous grant of Create/Update/Delete on a folder share | Covered by ACL anonymous tier + existing allow_upload |
| Default shares | Admin "Default share shortcuts": chip-input of share IDs with search; new users get share-shortcut objects (folder icon + share badge) in home root | setting.default_shares = share ID list; on user create → insert share-shortcut fs entries |
Storage policy management
| Feature | What the UI reveals | Implementation shape |
|---|---|---|
| Multiple policies per group | Group editor "Available storage policies" — multi-select chips of all policies; "Modifying will not affect uploaded files" | group_storage_policies join table replaces storage_policy_id single |
| Per-directory policies | FM toolbar policy button (battery icon) → dropdown of group-allowed policies, check = current dir's policy | file.metadata.policy_id or per-dir row; upload session resolves dir→policy |
| Load balancer | "Load Balance" appears in the provider type grid beside Local/Remote/S3…; editor = name + child-policy list with Weight number per child + Add/Remove | New StoragePolicyTypeLB; driver picks weighted child per upload session |
| File migration | User-facing: context menu → More actions → "Relocate storage policy" → policy dropdown → background task "Queue → Index files → Transfer → Commit changes" | We have admin variant (#175); extend to user scope limited to group-allowed policies |
User & auth
| Feature | What the UI reveals | Implementation shape |
|---|---|---|
| Multi-account | "Select an account" page: remembered sessions, avatar+email+trash, "Signed out" badges, "Use another account"; avatar menu = inline switcher | Frontend session store holds N token sets; swap active credential; sign-out revokes one |
| OIDC SSO | Admin "Third-party sign-in": ClientID/secret/Scope, "OIDC Wellknown Config — Import from URL", "Sign-in method name" (i18n-capable), "Login without registration" (auto-provision, locks user to that IdP) | Mostly landed (#141) — verify wellknown import + display-name parity |
| Logto SSO | Same panel, Logto-flavored preset | Falls out of generic OIDC — needs no extra backend; document Logto setup |
| QQ Connect | Separate OAuth (non-OIDC) provider button | Own small provider; low priority outside CN audience |
| Email filtering | "Filter email provider" (Whitelist/Blacklist) + domain list textarea + "Disable sub-address email" (blocks + aliases); registration-only, SSO exempt; toast "This email provider is forbidden" |
Landed (#141) |
Monetization (VAS)
| Feature | What the UI reveals | Implementation shape |
|---|---|---|
| Storage SKUs | "Edit SKU": name, size+unit, duration seconds (packs expire!), price in min currency unit (700=¥7), optional Label badge, "Allow paying with points"+points price | sku entity; purchase → capacity grant with expiry |
| Membership SKUs | "Edit group product": name, Group ID dropdown (upgrade target), duration, price, label, description (one bullet per line), points toggle | Same SKU entity, type=group, grants group for duration then reverts |
| Credits | Settings gets Finance tab: balance + Recharge; ledger table (±Change, Time, Reason: Shop purchase/Manual adjustment/Share link purchased) | credit_account + credit_txn ledger; admin manual adjust endpoint |
| Redemption | Shop→Redeem: code field (UUID fmt). Admin Gift Codes table: product type (Points/VIP/Storage), qty, code, Used/Available, × revoke. "Generate": count + product type + SKU + units-per-code | gift_code entity → redeem applies SKU/points atomically |
System extensions
| Feature | What the UI reveals | Implementation shape |
|---|---|---|
| Activity log | Per-file "Activity" dialog (context menu): actor avatar+time, typed actions ("Updated file content → Look for this version", "Moved from Trash to My Files", "Triggered thumbnail generation", "Was copied from X to His files") — cross-actor (collaborators' ops on shared files). Admin "Event details": ID/Type/IP/Time/Correlation ID/linked user+file+blob+share/UA/raw JSON | activity_event entity: type enum, actor, correlation_id, subject edges (user/file/blob/share), ip+ua, json payload; per-file feed + admin feed |
| Site announcements | Post-login modal: title+body, "Don't show anymore" + OK | setting.announcement (markdown); per-user dismissal bit |
| Node selection | Group "Allowed nodes" chips (empty = all); task dialogs get "Target node" dropdown — Auto dispatch default + allowed nodes; covers remote-download + compress/decompress; other tasks → master | group.allowed_nodes; task-create accepts target_node, scheduler validates against group list |
| Report abuse | Dialog on shares/users: target chip, Reason dropdown ("Copyright infringement"…), description, CAPTCHA | abuse_report entity + admin review queue (resolve/dismiss/block share) |
1.4 Org repo decisions — monorepo
Everything ships from Dvorinka/cloudreve. No submodules, no sibling repos.
cloudreve/ Go backend (existing code, repo root)
├── frontend/ web SPA — vendored from cloudreve/frontend (was `assets` submodule)
├── desktop/ Tauri app — vendored from cloudreve/desktop
├── android/ native Android app — Kotlin + Compose (Phase E)
└── .github/workflows/ GitHub Actions CI/CD (replaces azure-pipelines)
| Repo | Verdict | Reason |
|---|---|---|
cloudreve (this fork) |
Keep — base + monorepo root | The core |
frontend |
Vendored → frontend/ |
Submodule replaced; source lives in-repo, CI builds it |
desktop |
Vendored → desktop/ |
Tauri+React; portable core (cloudreve-api, inventory, tasks, uploader, drive/sync) vs Windows-only glue (cfapi, shellext, win32_notif) |
docs |
Skip for now | Fork when we ship public docs |
docker-compose |
Vendored file | Root docker-compose.yml already exists; extend for dev stack |
taskqueue |
Skip | Dead since 2024; superseded by in-app queue |
remote-server |
Skip | Dead PHP-era remote; v4 has native remote nodes |
ios-feedback |
Skip | Tracker for closed-source iOS app; we do Android instead |
theme-editor, frontend_v2, v2 |
Skip | Archived/ancient |
1.5 Upstream issues — 137 open, grouped
| Group | Count | Examples |
|---|---|---|
| Bug — upload/download/sync | ~25 | #3574 trash_bin_collect OOM, #3454 PG FK on upload, #3118 WebDAV 500 on large files, #3005 WebDAV fragments, #2938 upload stuck |
| Bug — WebDAV | ~8 | #2878 mount path 404 after move, #3118, #3409 read-only groups |
| Bug — DB/migration | ~8 | #3452 MySQL HeatWave, #2880 unix socket, #2934 v3→v4 sqlite, #2981 PG18 |
| Enhancement — sharing/permissions | ~15 | #3555 preview-only shares, #3390 default share visibility, #3340 upload-only folders, #3033/#3032 multi-share ops |
| Enhancement — storage policy | ~8 | #3518 encrypt on relocation, #2961 enable/disable policy, #2262 site-wide migration |
| Enhancement — auth/SSO | ~7 | #3464 OIDC, #3056 auto-OIDC, #2179 TOTP manual key, #3479 IP whitelist |
| Enhancement — download/tasks | ~10 | #3491 advanced remote download, #3259 yt-dlp, #2427 download quota, #2270 cancel tasks |
| UX/polish | ~20 | #3288 breadcrumb restore, #3223 deselect on empty click, #3508 loop video, #3507 gallery names |
| Pro-related (becomes free here) | ~10 | #3572 ADFS OIDC, #3515 recurring billing, #3180 group-expiry downgrade, #3171 subaddress ban |
| Mobile/iOS requests | ~5 | #3003 captcha incompat, #2826 login fail, #2863 capacity 0KB — Android solves |
| Chinese-titled (mixed) | ~40 | folded into groups above after translation |
| wontfix by upstream (revisit) | ~10 | #2494 multi-group, #2883 slide captcha, #2842 file audit — candidates for us |
1.6 Upstream PRs — verdicts
| PR | Change | Verdict |
|---|---|---|
| #3524 | revalidate share/direct links after permission change (+185/-1, CLEAN) — fixes #3544, same class as GHSA-vx2m | Merge — security |
| #3549 | gorilla/websocket 1.5.0→1.5.3 | Merge — dep CVE hygiene |
| #2964 | nwaples/rardecode 2.1.0→2.2.0 | Merge — dep hygiene |
| #2851 | ulikunitz/xz 0.5.12→0.5.14 | Merge — dep hygiene |
| #3490 | IP range/CIDR filter in event logs (+668, 2 files) — fixes #3480 | Merge — small, self-contained |
| #3472 | OIDC provider support (+292/-6, 10 files) — fixes #3464, overlaps Pro SSO | Merge after review — strategic (free OIDC) |
| #2481 | multi-stage Dockerfile (+18/-2) | Review — conflicts w/ our own docker work likely; take if clean |
| #2507 | p2p QUIC (draft) | Skip — draft, scope creep |
| #2499 | multi-group users (draft, WIP) | Watch — big feature, matches #2494 wontfix; revisit when upstream matures it |
| #1802 | checksum on WOPI/text-edit update (draft, 2024) | Skip — stale draft |
2. Immediate actions (this change set)
- Analysis + this roadmap
- Enable Issues on fork; label taxonomy (
group:*,pro-free,security,revisit,epic) - Migrate all 137 upstream issues → fork #1–137 (translated, grouped, linked)
- Merge upstream PRs: #3524, #3472, #3490, #3549, #2964, #2851, #2481
go build ./...+go test ./...green (incl. upstream's own stale-test fixes)- Monorepo restructure:
frontend/+desktop/vendored,assetssubmodule removed,android/scaffolded - CI/CD rewrite: GitHub Actions
ci.yml(backend/frontend/desktop matrix) +release.yml(goreleaser→ghcr.io); azure-pipelines removed
3. Phase A — foundation hardening (first weeks)
- Sync-upstream automation: weekly
upstream → forkmerge workflow (.github/workflows/upstream-sync.yml) - Dependabot on the fork (Go, npm, cargo —
.github/dependabot.yml) docker-compose.dev.ymldev stack (postgres + redis + backend built from source; frontend viayarn devhot reload)- Remove
ProDialog/ProChipgates infrontend/— all upsell interception stripped,ProDialog.tsxdeleted (backend feature work remains, Phase B) NOTICEattribution file — upstream authorship + independent-Pro-implementation statementgo vet ./...clean (upstream lint debt repaired: lock-by-value receivers, unkeyed literals, GobDecode signature)
4. Phase B — Pro features, free (the big one)
Order = user-visible value first; each ships with backend + UI + tests.
- Share collaboration — write/upload/delete via share link, anonymous upload, share ACL (users/groups), preview-only mode (fixes #3555, #3390, #3340, #3517, #3578; uses
NavigatorCapabilityplaceholder slots +SharePropsextension +shareentity fields). See §1.3a for the extracted UI spec.- PR #140 —
allow_upload/allow_edit/preview_only/upload_onlyprops, props-derived capability sets enforced server-side (writePermitted), same-share move/copy, anonymous upload, drop-box listing suppression, download denial viaIsDownloadCtxKeyhooks (fixes #3555 preview-only, #3340 drop-box) - File/dir ACL entity —
(subject_type ∈ user|group|anonymous|everyone, subject_id)→ R/C/U/D bitmask; Permissions dialog under More actions; enforced in share-navigator capability checks; group bit 15 gate (#181, fixes #3517) - Default shares —
setting.default_symbolics+ groupdefault_pinnedchip-input of share IDs; materialize as share-shortcut entries on fs init (#180) - Paid shares —
share.price_points+share_purchase(buyer debit → owner income atshare_score_rate) + resume ticket; download/thumb gated in share navigator, listing stays visible;share_sell(bit 29) gates price-setting,share_free(bit 8) bypasses paywall;PaidShareGateUI + restore viapurchase_ticket - Hash dedup / instant upload —
entity.hash+(hash,size)index;FindEntityByHash(completed version entities, owner scope default /globalopt-in);PrepareUploadlinks existing entity (refcount++) →rapid_uploadedsession, no transfer; media-meta/FTS queued per file; frontend streams SHA-256 viahash-wasm, skips encrypted policies;upload_dedup_scopeadmin setting (#191, fixes #3044) - Multi-file share — one share covering N selected files (upstream #3032): share.files M:N join (anchor file edge preserved for validity/ACL/paid gate); synthetic union root in share navigator with per-file real parent chains; dead linked files drop out of listing; upload-only + write caps stripped at union root; uris[] create API (max 50, dedup); multi-select Share dialog; file_count + anchor source_uri in share info
- PR #140 —
- Storage policy advanced — multiple policies per group, per-directory binding, load-balancer policy, file migration (fixes #3518, #2961, #2262). See §1.3a.
- PR #175 — resumable admin relocation task (entities or whole-policy scope), encryption-aware re-wrap, admin UI + per-policy migrate action (#9, #125, #136)
- Group→policies M:N (
allowed_policiesedge, empty = legacy single) + group-editor multi-select; per-directorysys:preferred_policymetadata marker with nearest-ancestor precedence (invalid marker cuts inheritance); userpreferred_policysetting applied in own tree only;load_balancepolicy type with weighted children resolved before drivers (#182, fixes #2961) - User-facing relocate —
POST /file/relocatefrom FM More-actions dialog, entity expansion + same-policy skip + dedup, restricted to group-allowed policies (#182, fixes #2262)
- SSO — generic OIDC, Logto, multi-account switching, sign-up email filtering (fixes #3464, #3056, #3505). See §1.3a.
- PR #141 — inbound OIDC consumer (auth-code + nonce, JWKS-verified RS256 id_tokens, userinfo fallback, auto-provisioning, one-time ticket handoff, SSRF-validated endpoints, redacted secret); covers Keycloak/Authentik/Logto/generic IdPs; sign-up email domain filtering (whitelist/blacklist + sub-address block) enforced at registration and SSO provisioning; multi-account lands via existing session
upsert(fixes #3464, #3056) - Multi-account switcher UI — N-token session store, avatar-menu switch + signed-out badges (#189)
- QQ Connect (non-OIDC protocol, separate integration), account linking UI for existing local accounts —
sso_bindingentity, link/unlink with lockout guard, admin accordion, sign-in button (#190); remainder: group/role claim mapping
- PR #141 — inbound OIDC consumer (auth-code + nonce, JWKS-verified RS256 id_tokens, userinfo fallback, auto-provisioning, one-time ticket handoff, SSRF-validated endpoints, redacted secret); covers Keycloak/Authentik/Logto/generic IdPs; sign-up email domain filtering (whitelist/blacklist + sub-address block) enforced at registration and SSO provisioning; multi-account lands via existing session
- VAS/monetization-free — credits + redemption codes as free features (gift codes for admin use), storage/membership SKU definitions; payment processors stay out of scope (fixes #3231). See §1.3a for the SKU/credits/gift-code spec.
skuentity (storage-capacity + group-upgrade types, duration, cash+points price, label, bullets); points purchase → atomic debit+grant; admin SKU tables;/shoppage (Memberships/Storage/Redeem tabs) + nav entryuser.credits+credit_txnledger (guarded atomic adjust); Finance settings tab (balance + grants + redeem + ledger); admin manual adjust (#183)gift_codeentity (points/storage/group × amount × duration) +user_grantexpiring grants +grant_expirecron; admin generate/list/revoke + user redeem (#183)- Paid-share
price_pointswired to the ledger + purchase/resume-ticket flow
- System extensions — activity/audit log, site announcements, node selection, report-abuse queue (fixes #3480, #3479 IP whitelist). See §1.3a.
- PR #144 — task
creator_ipcapture with CIDR-capable admin filter (#115 OSS half), group remote-download quotas per count + per volume (#16), yt-dlp downloader provider (#88), progressive image preview (#113), v3 migratorDatabaseURLpassthrough (#42) activity_evententity (immutable, tx-aware, actor+IP+CID) + per-file Activity dialog + admin/admin/eventfeed + per-type enablement + retention cron (#184)- Coverage wave 2: email/user-activated/token-refresh/share-viewed/version/metadata/view/thumb/live-photo/copy-from/webdav/profile+security/oauth/admin-ops/import (
1bbaddf) - link/unlink_account events wired via
sso_bindingflows (#190) - Event coverage wave 3:
membership_unsubscribeemitted by grant-expiry cron for reverted group grants;user_exceed_quota_notifiedat every quota rejection (upload pre-check, atomic reserve, copy) - Event coverage remainder (needs unbuilt features): payment_* (no payment processor), mount (policy-mount feature absent)
- site announcement:
announcementsetting (markdown) + post-login modal + per-user dismissal re-triggering on content change (#184) abuse_reportentity + publicPOST /abuse/report(IP rate-limit +abuse_captchagate) + admin/admin/abusequeue (resolve/dismiss + reversible share block) + share-menu Report entry (#185)- group
allowed_nodespool +allow_select_node+ tasktarget_nodedispatch (persisted in task state, weighted LB within pool); group admin multi-select + task-dialog node picker
- PR #144 — task
5. Phase C — security + quality
- Own security review on top of upstream fixes: session/token entropy audit, SSRF guard re-test (NAT64 class), rate limiting on auth endpoints
- Fix upstream bug backlog by impact:
#3574 OOM(done — paged tree walk + batched delete),#3118/#3005 WebDAV large-file(done — Content-Range assembly into one session; non-local policies get honest 501; single-PUT giant-file 500s are proxy/client timeouts, not fixable server-side),#3375 SMTP auth discovery(done —smtp_authsetting) - #3454 (PG FK on upload) is Pro-only —
audit_logsdoesn't exist in this codebase. When B.5 adds our own audit log: insert the audit row in the same tx after the file row, never before. - #198 (upstream #3581) — "import files" task shows source storage policy "unknown":
ImportTaskState.PolicyNamebaked at creation (resolved best-effort viaStoragePolicyClient), summary emitsdst_policy_nameso admin views of other users' tasks work without a policy lookup;policyOptionCacheretyped toStoragePolicyBrief[]and populated fromgetAllowedPolicies()at session init as fallback for legacy tasks - #199 (upstream #3584) — markdown editor lag: root cause was per-keystroke React re-renders (changedValue state fed back into the editor's initial-markdown prop) re-running every plugin's
update()hook (RealmWithPlugins has a dep-less effect).MarkdownEditoris nowmemo'd with auseMemo'd plugins array + stabletranslation;MarkdownViewerkeeps edits in a ref (read at save) and passes the immutable loaded content — zero re-renders per keystroke - #200 (upstream #3586) — SIGHUP "crash": the upstream log was a clean signal-driven shutdown (SIGHUP was registered in
signal.Notify), triggered when the reporter's terminal/SSH session closed.signal.Ignore(syscall.SIGHUP)now — default disposition would terminate the process; verified live: server surviveskill -HUP(HTTP stays 200) and still shuts down cleanly on SIGTERM - #89 (upstream #3277) — Android Motion Photo preview:
util/motionPhoto.tsextracts the MP4 appended at EOF of MicroVideo/MotionPhoto JPEGs — XMPGCamera:MicroVideoOffsetorContainer:Directoryvideo/mp4Item:Length, fetched via Range requests (128 KiB head + video tail only),ftypsanity-checked;Photo.tsxfeeds the blob URL into the same LivePhotosKit player used for iOS live photos — badge + press-to-play for free; degrades silently on CORS/fetch failure desloppifypass — 73 review items dispositioned (46 fixed, 27 honestly skipped), strict score 77.1 (was 18.9); scorecard lives in README.security-reviewerpass done incrementally per batch (OAuth secrets, SSRF, process exec, path safety)- Tag management page (upstream #2962) — owner-scoped
tag:metadata stats/rename/recolor/delete ininventory.FileClient,GET/PATCH/DELETE /file/tagroutes, Settings → Tags tab with merge-on-rename semantics - Download URL shuffling (#173) —
download_cdn_shuffledistributes generated download URLs randomly across the site URL +download_cdn_routesendpoints (setting.DownloadURLBase, honorsUseFirstSiteUrl); covers entity downloads, archive sessions, and redirect-type direct links; manual route picker hidden client-side while active - Private space / vault (upstream #3447) — opt-in root folder flagged
sys:vault; ancestry-based membership (zero flag maintenance; chain-less search results resolved lazily viafile_children);vaultNavigatordecorator gatingTo/Children/Walk/ExecuteHook; separate vault password (salt:sha256, sensitive) + 30-min cache-backed unlock session, unlock rate-limited 10/h; vault content never shareable and never direct-linkable; search filtered while locked;vault_enabled/vault_unlockedin user settings; unlock prompt inExplorerError, Private space section in security settings, lock badge on vault folder (#195) - Saved share links (#147) —
POST /file/createacceptstype: share+share_id/share_password, materializing a symbolic shortcut (sys:shared_redirect) that lists under My Files and Shared with me; "Save to my files" in the share popover + "Save share link" dialog on /shares - Share
hide_readmeoption (upstream #2729 item 6) —ShareProps.HideReadMe(only meaningful withShowReadMe); share navigator filtersREADME.md/README.txt(case-insensitive) from listings while direct-path resolution stays open for the readme viewer;detectReadMeURI fallback now probes unconditionally; owner-onlyhide_readmeon share responses; Share dialog nested checkbox - 2FA recovery codes (upstream #2729 item 3) —
user.two_factor_backup_codessensitive JSON ofsalt:sha256digests;PUT /user/setting/2fa/backupregenerates 10 one-time codes behind a valid TOTP (rate-limited 5/h);Verify2FAfalls back to single-use code consumption on TOTP failure; codes invalidated on secret rotation/disable; login phase gains a recovery-code input mode; security settings show remaining count + regenerate dialog - Public share directory (upstream #2729 items 4+5) —
share.listed_publiclyopt-in column gated by newGroupPermissionSharePublicListgroup bit; rejected on password-protected shares at the service layer and normalized off at creation;GET /share/listedanonymous endpoint (rate-limited 60/min/IP, cursor pagination) listing only non-expired passwordless listed shares with case-insensitive name search across anchor + covered files;listed_publiclyowner-visible in share responses;/discoverpage (anonymous-visible nav item + sign-in link), admin group Share section switch, en+zh locales - Decompression-bomb guards (meta #2 item 10) —
DecompressSizenow bounds cumulative extracted output (its documented "total file size" intent), not just compressed input:checkExtractGuardsaborts at the limit and at a 100k-entry cap (maxExtractEntries, bounds dir-creation bombs), each entry stream wrapped incappedFileso understated size headers cannot overrun; slave path receives the limit viaSlaveExtractArchiveTaskState.ExtractLimit; all failures carryqueue.CriticalErr(no retry of the same bomb); resume-safe via cursor-skip size accounting - Storage policy total capacity (upstream #2178 item 1) —
PolicySetting.MaxTotalSizecaps cumulative entity bytes per policy; enforced inPrepareUpload, batch upload validation, andcopyFiles(baseline usage + per-batch accumulation since tx writes are invisible to the usage query); canonicalErrInsufficientCapacitypreserved soerrors.Isquota handling still matches; admin policy editor gains a Max total capacity SizeInput, en+zh locales - Storage policy overflow chain (upstream #2178 item 4) —
PolicySetting.OverflowPolicyIDlinks a fallback policy;overflowChainwalks hops with cycle guard + hop cap, skipping suspended members and resolving load-balance members to weighted children;PrepareUploadspills to the first member with headroom for the file size so name/size/extension rules apply to the landing policy;PreValidateUploadchecks aggregate chain headroom since batches may split across members; admin policy editor gains an Overflow policy select, en+zh locales
6. Phase D — desktop, all platforms
Goal: Windows + macOS + Linux from the desktop/ tree in this repo.
| Layer | Windows (exists) | macOS | Linux |
|---|---|---|---|
| Placeholders/hydration | cfapi (keep) | File Provider ext (Swift bridge) | FUSE (fuser) or plain sync folder |
| Shell integration | shellext (keep) | Finder sync extension | Nautilus/Dolphin plugin (later) |
| Notifications | win32_notif | mac_notification_sys |
notify_rust |
| Sync core | shared: cloudreve-api, inventory, tasks, uploader, drive/sync |
same | same |
- Status: (1) notifications already per-OS (
win32_notif/notify_rust/mac_notification_sys) — no abstraction needed; (2) hydration abstracted viadrive/placeholdercfg swap —cfapion Windows,placeholder_non_windowsfull-sync adapter elsewhere (FUSE / File Provider still open); (3) CI matrix builds + tests all 3 OSes; (4) packaging:desktop-release.ymlondesktop-v*tags ships .msi/.exe (Windows), .dmg (macOS), .deb/.AppImage (Linux) — MSIX deferred (needs store signing). - Verified on Linux:
cargo test --workspacegreen (49 tests),cargo tauri buildproduces working .deb + .AppImage. - Feature fallback on Linux/macOS until providers land: full sync without placeholders (download-on-access still works via sync engine).
- #167 (upstream desktop#49) — online-only thumbnails missing in Explorer: root cause was a client/server contract mismatch — the CE
/file/thumbresponse carries onlyurl/expireswhile thecloudreve-apimodel requiredobfuscated, failing deserialization on every thumbnail request (E_FAILto Explorer; hydrated files were unaffected since Windows thumbs them locally).obfuscatedis now#[serde(default)]; the decode path still runs when a server emits the flag
7. Phase E — Android app (native, no iOS)
Lives in android/ in this repo. Kotlin + Jetpack Compose, Material 3.
- API:
api/v4REST + OAuth token (entities exist:oauthclient,oauthgrant) — same surface the desktopcloudreve-apicrate documents; port its models as the spec - Core features: browse/download/upload files, share links, full-text search (done — query bar + offset pagination + parent-path/snippet rows), camera-upload (done — periodic WorkManager MediaStore sync, Wi-Fi-only constraint, ID dedup, settings dialog), offline-favorite files (done — "Keep offline" downloads to filesDir/offline, DataStore registry, star dialog with open/refresh/remove), local sync folder (done — SAF tree → remote mirror, upload-only, mtime/size dedup)
- System integration (the "native, complete" ask): share-sheet target (done — SEND/SEND_MULTIPLE → UploadWorker), DocumentsProvider (done — Files-app browse/open/thumb/rename/delete/search), quick-share tile, notifications on share/task events (done — periodic /workflow poll → terminal-state notifications)
- Auth: OAuth flow (done — browser consent + PKCE +
cloudreve://mountdeep link); later passkey if backend exposes - WebDAV bridge:
/davworks as fallback file access until SDK matures - Non-goals: iOS, tablet-first layouts (works, not optimized)
8. Governance
- License/credit: keep
LICENSE(GPL-3.0), addAUTHORS/credit line to original Cloudreve authors in README — attribution without endorsement - Release cadence: tag
fork-4.19.xline first (cherry-picks only), then5.0.0-forkonce Phase B lands - Every merge: build + test + lint green (pre-push gate, non-negotiable)
Issue migration format
Each fork issue: title translated to English when needed, body = Upstream: cloudreve/cloudreve#NNNN + short restatement + group labels. Epics get epic label and link children. Upstream wontfix items we want get revisit label.