GoDrive
A self-hosted file storage and sync platform with a Go REST backend and a Flutter mobile client. Files are chunked via the TUS resumable upload protocol so large transfers survive network interruptions. Thumbnails are generated and cached with inode-stable references, metadata lives in SQLite, and webhook events are signed with HMAC-SHA256. Multi-arch Docker images support both x86 and ARM homelabs.




goDrive treats the filesystem as the source of truth — SQLite only stores metadata: sessions, the search index, trash records, and in-flight upload state. Everything else is rebuildable by reindexing the disk. Uploads go through the TUS resumable protocol, so a dropped connection on a phone resuming a 4 GB video upload just picks back up rather than restarting. Once a file lands, thumbnails are generated asynchronously across several warmup sizes and cached under a key derived from the file's inode and device number rather than its path — renames and moves on the same filesystem don't invalidate the cache, only a tracked SQLite path lookup needs to update. A webhook system lets external automation react to file events (upload.complete, file.moved, file.deleted, file.restored), each delivery signed with HMAC-SHA256 so a subscriber can verify it actually came from goDrive. The same Go backend serves a Svelte web UI, a WebDAV mount for native OS clients, and a Flutter mobile app with background upload support on both Android and iOS.
Architecture
graph TD
Browser["Svelte / SVAR web UI"] -->|REST + SSE| API[Go backend]
Mobile["Flutter app (Android/iOS)"] -->|TUS resumable upload| API
WebDAV["Finder / Files / rclone"] -->|WebDAV| API
API --> FS[("Filesystem
(source of truth)")]
API --> DB[("SQLite
sessions · index · trash · upload state")]
API --> Cache[("Inode-keyed
thumbnail cache")]
API -->|HMAC-SHA256 signed| Hooks["Webhook subscribers"]Under the hood
Thumbnails are cached by inode+device, not by path — a file renamed or moved on the same filesystem still hits its existing cached thumbnail instead of regenerating it.
func thumbnailCachePathInode(cacheRoot string, userID int64, logical string, info os.FileInfo, thumbSize int) string {
inode, device := inodeKey(info)
if inode == 0 {
return thumbnailCachePath(cacheRoot, userID, logical, info.Size(), info.ModTime().UnixNano(), thumbSize)
}
sum := sha256.Sum256(fmt.Appendf(nil, "%d\x00%d\x00%d\x00%d\x00%d\x00%d\x00%d",
thumbnailCacheVersion, userID, inode, device, info.Size(), info.ModTime().UnixNano(), thumbSize))
return filepath.Join(cacheRoot, "thumbs", hex.EncodeToString(sum[:2]), hex.EncodeToString(sum[:])+".jpg")
}