A self-hosted cloud storage platform that puts you in control. Access, manage, and share your files from anywhere β no subscriptions, no third parties, no limits.
Imagine having your own personal Google Drive β no data leaving your server, no monthly fees, no privacy concerns. That's what Imaginary Storage gives you. It's a complete file management solution that runs on your own Linux machine, accessible through a sleek web dashboard that feels just like the cloud services you're used to.
Whether you're managing files on a home NAS, sharing projects with teammates, or building a backup system, Imaginary Storage handles it all β uploads, downloads, file previews, trash management, user accounts, disk mounting, and even AWS archival β all wrapped in a fast, modern interface.
- Features
- Architecture
- Project Structure
- Getting Started
- API Reference
- Security Model
- User Data Directory
- Testing
- CI/CD
- Background Threads
- Frontend Architecture Notes
- License
- π Secure by Design β every user operates in their own isolated environment. When you log in, the server runs your requests as you β using your actual Linux user account. No shared processes, no permission hacks.
- π Web Dashboard β a fast, modern interface that works in any browser. Grid or list view, dark mode, drag-and-drop uploads, right-click menus, keyboard shortcuts β everything you'd expect from a cloud storage app.
- π€ Resumable Uploads β uploading large files over unstable connections? No problem. Files are split into chunks, verified with SHA-256 hashes, and automatically resumed from where they left off.
βοΈ File Sharing β share files and folders with other system users. Set read-only or read-write permissions, add expiration dates, and revoke access anytime.- ποΈ Trash System β accidentally deleted something? Every delete moves files to a trash folder with full metadata. Restore in one click, or empty the trash when you're sure.
- βοΈ AWS Backup β automatically archive folders to Amazon S3 Glacier or Deep Archive. Set it and forget it β the built-in scheduler handles the rest.
- π₯οΈ Admin Panel β manage Linux users (create, edit, delete) and physical disks (mount, unmount, format) right from the web interface. No SSH required.
- β‘ Performance Built In β a lightweight C server handles the heavy lifting. Small memory footprint, fast response times, no bloated frameworks on the backend.
- HTTP Framework: Custom minimalist
chttpframework (lib/chttp.c/lib/chttp.h) - Language: C99 (compiled with GCC,
-Wall -Wextra) - Entry Point:
src/main.cβ route registration, auth wrappers, background scheduler init - Dependencies:
libpam(authentication),libacl(file sharing ACLs),pthreads(background schedulers) - Vendored:
cJSON(JSON parsing/generation),sha256(chunked upload verification)
- Server starts as
root, creates./sessions/(mode 0700). - Route handlers are wrapped with
DEFINE_AUTH_ROUTE,DEFINE_STREAM_AUTH_ROUTE, orDEFINE_NOPRIV_AUTH_ROUTE. - Auth wrappers validate the
active_sessioncookie (token + username cross-check). DEFINE_AUTH_ROUTE/DEFINE_STREAM_AUTH_ROUTE: callfork_and_run()/fork_and_stream()β fork child,setuid/setgidto the user,chdirto home, run handler, write response,_exit(). The parent waits with a timeout (30s / 1h) and reaps the child.DEFINE_NOPRIV_AUTH_ROUTE: runs handler directly in the connection thread (as root) β used for session management and control-plane operations that need to write root-owned files.
Routes are registered in src/main.c via convenience macros:
CHTTP_GET(&srv, "/path", handler)
CHTTP_POST(&srv, "/path", handler)
CHTTP_PUT(&srv, "/path", handler)
CHTTP_DELETE(&srv, "/path", handler)
CHTTP_STREAM_GET(&srv, "/path", handler) // 1-hour timeout
CHTTP_STREAM_POST(&srv, "/path", handler) // 1-hour timeout
CHTTP_HEAD(&srv, "/path", handler)Source files in src/, lib/, and vendor/ are auto-discovered by the Makefile.
- Framework: React 19, TypeScript, Vite 8
- Styling: Tailwind CSS v4 +
tw-animate-css+ shadcn/ui (base-nova style) - State: Redux Toolkit (11 slices)
- Routing: React Router v7
- Icons: Lucide React, material-icon-theme (file type icons)
- Dark Mode: next-themes (
attribute="class", persisted to localStorage) - Toasts: Sonner
- Fonts: Plus Jakarta Sans (Google Fonts), JetBrains Mono (monospace)
- Build Output:
frontend/β../www/(served by the backend as static files) - File Viewer: Viewer page at
/viewdetects file type (image,video,audio,pdf,text) by MIME + extension usinglib/fileTypes.ts - File Icons:
material-icon-thememapping incomponents/files/fileIcons.tsβ maps file extensions to Material Design icons - Utils:
cn()utility viaclsx+tailwind-merge
βββ lib/ # Custom C HTTP framework (chttp.c / chttp.h)
βββ vendor/ # Vendored libraries (cJSON, SHA-256) β do not modify
βββ src/
β βββ main.c # Entry point, route registration, auth wrappers
β βββ auth/ # PAM authentication, session management, fork_and_run
β β βββ auth.h # Auth macros (DEFINE_AUTH_ROUTE, etc.)
β β βββ auth.c # authenticate_pam(), fork_and_run(), fork_and_stream()
β β βββ session.c # Session create/validate/delete/parse, atomic writes
β βββ routes/
β β βββ routes.c/.h # Login, whoami, legacy test routes
β β βββ session_mgmt.c # GET/DELETE /sessions, POST /sessions/switch, logout
β β βββ static.c/.h # Static file server (Range, ETag, SPA fallback)
β βββ fs/
β β βββ fs.c/.h # All /fs/* handlers (list, upload, download, mkdir,
β β β rename, move, copy, stat, content read/write,
β β β streaming upload, chunked resumable upload)
β β βββ trash.c/.h # Trash system (move-to-trash, restore, delete, empty)
β βββ admin/
β β βββ admin.c/.h # User CRUD (via useradd/usermod/userdel/chpasswd)
β β βββ disk.c # Disk list/mount/unmount/format (via lsblk/mount/umount/mkfs)
β βββ share/
β β βββ share.c/.h # File sharing via POSIX ACLs (registry, apply, revoke)
β β βββ sweeper.c # Periodic expired-share cleanup thread
β βββ aws_sync/
β β βββ aws_sync.h # AWS S3/Glacier sync API + scheduler interface
β β βββ config.c # Config CRUD, manual sync trigger (pthread worker)
β β βββ scheduler.c # Background scheduler (scans /home every 60s)
β βββ utils/
β βββ utils.c/.h # mime_from_ext, safe_path, safe_filename, fs_error,
β parse_multipart
βββ frontend/ # React SPA
β βββ src/
β βββ main.tsx # Provider, ThemeProvider, Toaster, TooltipProvider
β βββ App.tsx # Router, ProtectedRoute, route definitions
β βββ index.css # Tailwind v4 + CSS variables (light/dark theme)
β βββ types/api.ts # TypeScript API response types
β βββ api/ # API client modules (client, auth, filesystem, trash,
β β sessions, admin, awsSync, share)
β βββ store/
β β βββ index.ts # configureStore (11 reducers)
β β βββ hooks.ts # Typed dispatch/selector hooks
β β βββ slices/ # auth, fileSystem, uploads, trash, sessions,
β β bookmarks, settings, users, disks, awsSync, shares
β βββ lib/ # uploadEngine, fileTypes, utils (cn)
β βββ components/
β β βββ layout/ # AppShell, TopBar, Sidebar
β β βββ files/ # FileBrowser, FileGrid, FileList, FileCard, FileRow,
β β β Breadcrumbs, PlacesPanel, Toolbar, FilePreview,
β β β FileContextMenu, DeleteConfirmDialog, FileIcon,
β β β fileIcons, DownloadDialog, NewFolderDialog, RenameDialog
β β βββ uploads/ # UploadManager
β β βββ auth/ # LoginForm
β β βββ sessions/ # SessionList, SessionCard
β β βββ admin/ # UserList/Dialog/Card, DiskList/Card, Mount/Format Dialog
β β βββ share/ # ShareDialog
β β βββ settings/ # AwsSyncCard, FolderPickerDialog, ManageSharesCard
β β βββ ui/ # 15 shadcn/ui primitives (button, dialog, tooltip, etc.)
β βββ pages/ # LoginPage, DashboardPage, SettingsPage,
β FileViewerPage, UsersPage, DisksPage
βββ landing/ # Separate landing page (Vite + React + Tailwind)
βββ scripts/ # Shell test scripts (17 scripts, one per endpoint + runner)
βββ poc/ # Proof-of-concept HTML for resumable upload testing
βββ journal/ # Implementation plans and notes
βββ prompts/ # Session prompts for AI-assisted development
βββ build/ # Intermediate build artifacts (.o / .d) β gitignored
βββ dist/ # Compiled server binary β gitignored
βββ www/ # Frontend build output β gitignored
βββ sessions/ # Session files (created at runtime) β gitignored
βββ uploads/ # Legacy upload fixture β gitignored
βββ .github/workflows/ # GitHub Actions CI/CD
βββ Makefile # Backend build system
βββ run # Build + run script (make + frontend build + sudo)
- OS: Linux (required for PAM,
setuid, POSIX ACLs,mount/umount/mkfs) - Compiler: GCC with
make - System Dependencies:
libpam-dev,libacl1-dev,pthreads - Node.js: v20+ (for frontend)
- Runtime Dependencies:
sudo(server runs as root),aws-cli(for S3 sync)
Backend:
make # compiles dist/server (zero-warning build)
make clean # removes build/ and dist/Frontend (development):
cd frontend
npm install
npm run dev # Vite dev server on :5173, proxies API to :8080
npx tsc --noEmit # type-check onlyFrontend (production):
cd frontend
npm run build # vite build β ../www/Run the server:
./run # builds everything and starts with sudo
# or: sudo ./dist/serverThe server listens on port 8080 by default.
The ./run script: builds the server (make), builds the frontend (cd frontend && npm run build), then starts with sudo ./dist/server.
The .envrc file (for direnv) adds $PWD and $PWD/scripts to PATH, and sources a .env file if present.
All authenticated endpoints (except /login) require the active_session cookie set by a successful login.
| Method | Path | Description |
|---|---|---|
POST |
/login |
PAM authentication, sets session_<user>=<token> and active_session=<token>/<user> cookies |
GET |
/whoami |
Returns {uid, gid, username, home, shell, cwd} β runs in forked user context |
DELETE |
/logout |
Deletes active session, clears all session cookies |
| Method | Path | Description |
|---|---|---|
GET |
/sessions |
Lists all sessions found in cookies |
DELETE |
/sessions/:session_id |
Deletes a specific session (owns via session_<user>=<token> cookie) |
POST |
/sessions/switch/:session_id |
Switches active_session to a different existing session |
All paths are relative to the authenticated user's home directory (chdir to home after privilege drop).
| Method | Path | Description |
|---|---|---|
GET |
/fs/list?path=. |
List directory entries (name, type, size, modified, mime) |
POST |
/fs/upload?path=. |
Simple multipart file upload |
GET |
/fs/download?path=... |
Download file (STREAM route, supports ?inline=1) |
DELETE |
/fs/file?path=... |
Moves to trash (soft delete) |
POST |
/fs/mkdir |
mkdir -p, body: {"path":"..."} |
DELETE |
/fs/dir?path=... |
Moves to trash (soft delete) |
POST |
/fs/rename |
Body: {"path":"...","name":"..."} |
POST |
/fs/move |
Body: {"from":"...","to":"..."} |
POST |
/fs/copy |
Body: {"from":"...","to":"..."} |
GET |
/fs/stat?path=... |
File/directory metadata (name, type, size, mode, uid, modified, mime) |
GET |
/fs/content?path=... |
Read text file (β€64 KB), returns {"path","content"} |
PUT |
/fs/content?path=... |
Write text file body as raw text |
POST |
/fs/upload-stream?path=... |
Streaming upload requiring Content-Length (STREAM route) |
| Method | Path | Description |
|---|---|---|
POST |
/fs/upload-session |
Create upload session from JSON manifest β {upload_id} |
GET |
/fs/upload-session/:upload_id |
Query session status β {received_chunks[]} |
STREAM_POST |
/fs/upload-chunk/:upload_id |
Upload one chunk with X-Chunk-Index header (STREAM route) |
DELETE |
/fs/upload-session/:upload_id |
Abort and clean up |
Manifest fields: dest, filename, file_size, chunk_size, chunk_count, chunk_hashes[] (SHA-256 hex).
Temp storage: ~/.imaginary/uploads/<id>.meta / .state / .data.
On completion: rename(.data, dest), cleanup .meta / .state.
| Method | Path | Description |
|---|---|---|
GET |
/trash/list |
List trashed items |
POST |
/trash/restore |
Body: {"name":"..."} β restore to original path |
DELETE |
/trash/:name |
Permanently delete one trashed item |
DELETE |
/trash |
Empty entire trash |
Trash storage: ~/.imaginary/trash/files/<name> + ~/.imaginary/trash/info/<name>.info
Info format: path=<original>\ndeleted_at=<ISO8601>\n
Name collisions append .1, .2, etc.
| Method | Path | Description |
|---|---|---|
GET |
/admin/users |
List all system users (uid β₯ 1000 + root) |
POST |
/admin/users |
Create user (useradd -m, chpasswd) |
PUT |
/admin/users/:username |
Edit user (shell, groups, password via usermod/chpasswd) |
DELETE |
/admin/users/:username |
Delete user (userdel -r) |
GET |
/admin/disks |
List block devices (lsblk -Jbo) |
POST |
/admin/disks/mount |
Mount device β body: {device, mountpoint, fstype?} |
POST |
/admin/disks/unmount |
Unmount β body: {mountpoint} |
POST |
/admin/disks/format |
Format β body: {device, fstype} (runs mkfs.<fstype>) |
Per-user write-only archival to S3 Glacier/DEEP_ARCHIVE. Config stored at ~/.imaginary/config/aws-sync.json.
| Method | Path | Description |
|---|---|---|
GET |
/aws-sync |
Get current user's config (credentials redacted) |
PUT |
/aws-sync |
Save/update config β body: {enabled, folder, bucket, prefix, region, accessKeyId, secretAccessKey, storageClass, intervalMinutes} |
DELETE |
/aws-sync |
Delete config |
POST |
/aws-sync/run |
Trigger immediate sync (spawns detached pthread, returns 202) |
The background scheduler (aws_sync_scheduler_start) scans /home/*/.imaginary/config/aws-sync.json every 60s and runs aws s3 sync for enabled, due configs.
Shares are registered in a root-owned registry at /var/lib/imaginary/shares.json. Access is enforced by POSIX ACL entries on the shared paths. Recipient access requires a valid share ID β the ACL still provides the kernel-level gate.
| Method | Path | Description |
|---|---|---|
POST |
/share |
Create share β body: {source, recipient, kind, mode, expires_at?} |
DELETE |
/share/:share_id |
Revoke share (removes ACLs) |
GET |
/share/incoming |
List shares received by the caller |
GET |
/share/outgoing |
List shares created by the caller |
GET |
/share/users |
List sharable system users (uid β₯ 1000, excluding self) |
GET |
/share/:share_id/list?path=. |
List directory in shared path |
GET |
/share/:share_id/stat?path=. |
Stat file in shared path |
GET |
/share/:share_id/content?path=. |
Read text file in shared path (β€64 KB) |
PUT |
/share/:share_id/content?path=. |
Write text file in shared path (rw only) |
POST |
/share/:share_id/mkdir |
Create directory in shared path (rw only) |
DELETE |
/share/:share_id/file?path=. |
Delete file in shared path (rw only) |
STREAM_GET |
/share/:share_id/download?path=. |
Download from shared path (STREAM) |
STREAM_POST |
/share/:share_id/upload?path=. |
Upload to shared path (STREAM, rw only) |
The share sweeper thread (share_sweeper_start) checks every 60s for expired shares, revokes them, and garbage-collects entries revoked >30 days.
- Authentication: System PAM. Any local Linux user can log in with their system password.
- Sessions: Token-based, stored in
./sessions/session_<hex64>, root-owned (mode 0644). Tokens are 64 random hex bytes from/dev/urandom. Session files use atomic rename-based writes to prevent partial reads. - Privilege Drop: Each authenticated request forks; the child calls
initgroups(),setgid(),setuid()to become the user. The child verifies the drop is irreversible (attemptssetuid(0), must fail for non-root users). The parent waits with a timeout and kills hung children. - Path Safety:
safe_path()rejects..traversal.safe_filename()rejects/,\, and... All FS and share paths are validated. - File Permissions: No application-level permission simulation β the kernel enforces standard
rwxand ACL permissions based on the forked child's UID/GID. - Sharing: POSIX ACLs applied via
libacl. Parent-directory--xtraversal ACLs are added for shared paths. The share registry in/var/lib/imaginary/is root-owned and read-only by all.
~/.imaginary/
βββ places # Bookmarks file (tab-separated: path\tlabel)
βββ config/
β βββ settings.json # Frontend settings (viewMode, iconSize)
β βββ aws-sync.json # AWS sync configuration (mode 0600)
βββ trash/
β βββ files/ # Trashed files/directories (flat, collision-suffixed)
β βββ info/ # Sidecar .info files (path + deleted_at)
βββ uploads/ # Chunked upload temp storage (<id>.meta/.state/.data)
# Integration test suite (requires running server + valid user)
./scripts/runtests.sh <username> <password>Individual endpoint test scripts are in scripts/ (e.g., login.sh, fslist.sh, fstest.sh).
| Workflow | Trigger | Action |
|---|---|---|
landing-pages.yml |
Push to main touching landing/** or workflow file |
Builds landing/ with Vite, deploys to GitHub Pages via actions/deploy-pages@v4 |
The landing page is configured for a custom domain (public/CNAME). Build base is /.
| Thread | Purpose | Interval |
|---|---|---|
aws_sync_scheduler |
Runs aws s3 sync per user config |
60s tick |
share_sweeper |
Revokes expired shares, GCs old entries | 60s tick |
- Navigation:
navigateTo(path)callslistDirThunk(path).unwrap(), only updates URL on success. Prevents path spam from key-repeat. - Trash View: Pseudo-path
trash:///renders the trash interface instead of a real filesystem path. - Upload Engine: Singleton (
uploadEngineinlib/uploadEngine.ts) β sequential queue, one file at a time. 4 MB chunks, SHA-256 hashing via SubtleCrypto, exponential-backoff retry (max 10). Resume vialocalStorage(checks/fs/upload-session/:idforreceived_chunks).onChange/onCompleteevents. Pause/resume viatogglePause()(50ms poll). Per-itemAbortControllerfor cancellation. - Settings Persistence: Debounced save (500ms) via
/fs/contentAPI. - Session Switch:
suppress401Redirect()temporarily disables the 401βlogin redirect during the race window. - 401 Handling:
api/client.tsdispatchesauth:unauthorizedwindow event βProtectedRoutelistens β navigates to/login. - Vite Proxy: Dev server proxies
/login,/logout,/whoami,/sessions,/fs,/trash,/admin,/statictohttp://localhost:8080.
Distributed under the MIT License. See LICENSE for more information.
- Built as the BTech (IT) final year major project at [Your University Name]
- Thanks to our project supervisor and everyone who contributed