Skip to content

Latest commit

Β 

History

104 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Imaginary Storage (NAS)

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.

License: MIT C React TypeScript

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.


Table of Contents


Features

  • πŸ”’ 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.

Architecture

Backend

  • HTTP Framework: Custom minimalist chttp framework (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)

Process Model

  1. Server starts as root, creates ./sessions/ (mode 0700).
  2. Route handlers are wrapped with DEFINE_AUTH_ROUTE, DEFINE_STREAM_AUTH_ROUTE, or DEFINE_NOPRIV_AUTH_ROUTE.
  3. Auth wrappers validate the active_session cookie (token + username cross-check).
  4. DEFINE_AUTH_ROUTE / DEFINE_STREAM_AUTH_ROUTE: call fork_and_run() / fork_and_stream() β€” fork child, setuid/setgid to the user, chdir to home, run handler, write response, _exit(). The parent waits with a timeout (30s / 1h) and reaps the child.
  5. 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.

Route Registration

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.

Frontend

  • 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 /view detects file type (image, video, audio, pdf, text) by MIME + extension using lib/fileTypes.ts
  • File Icons: material-icon-theme mapping in components/files/fileIcons.ts β€” maps file extensions to Material Design icons
  • Utils: cn() utility via clsx + tailwind-merge

Project Structure

β”œβ”€β”€ 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)

Getting Started

Prerequisites

  • 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)

Build & Run

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 only

Frontend (production):

cd frontend
npm run build     # vite build β†’ ../www/

Run the server:

./run             # builds everything and starts with sudo
                  # or: sudo ./dist/server

The 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.


API Reference

All authenticated endpoints (except /login) require the active_session cookie set by a successful login.

Authentication

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

Session Management

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

File System

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)

Chunked Resumable Upload

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.

Trash

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.

Admin (Root Only)

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>)

AWS Sync

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.

File Sharing (POSIX ACLs)

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.


Security Model

  • 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 (attempts setuid(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 rwx and ACL permissions based on the forked child's UID/GID.
  • Sharing: POSIX ACLs applied via libacl. Parent-directory --x traversal ACLs are added for shared paths. The share registry in /var/lib/imaginary/ is root-owned and read-only by all.

~/.imaginary/ Directory Layout (Per-User)

~/.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)

Testing

# 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).



CI/CD

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 /.


Background Threads

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

Frontend Architecture Notes

  • Navigation: navigateTo(path) calls listDirThunk(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 (uploadEngine in lib/uploadEngine.ts) β€” sequential queue, one file at a time. 4 MB chunks, SHA-256 hashing via SubtleCrypto, exponential-backoff retry (max 10). Resume via localStorage (checks /fs/upload-session/:id for received_chunks). onChange/onComplete events. Pause/resume via togglePause() (50ms poll). Per-item AbortController for cancellation.
  • Settings Persistence: Debounced save (500ms) via /fs/content API.
  • Session Switch: suppress401Redirect() temporarily disables the 401β†’login redirect during the race window.
  • 401 Handling: api/client.ts dispatches auth:unauthorized window event β†’ ProtectedRoute listens β†’ navigates to /login.
  • Vite Proxy: Dev server proxies /login, /logout, /whoami, /sessions, /fs, /trash, /admin, /static to http://localhost:8080.

License

Distributed under the MIT License. See LICENSE for more information.


Acknowledgments

  • Built as the BTech (IT) final year major project at [Your University Name]
  • Thanks to our project supervisor and everyone who contributed

About

A self-hosted, high-performance Network Attached Storage (NAS) solution featuring a security-first C backend and a modern React frontend.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages