wellcome to my File Manager web application. this tools will make easy to upload, download file and it has a terminal to use control your linux
The File Manager Easy to use, It is opensource u can optimzie, customize your theme and you can pull request for your best edit to me. feature : Drang and Drop, formiddle, xterm, webssh, Nexjs, Manage User, Manager Root Folder.
webssh be create by huashengdun GahubLink and Me Modify style to Boostrap -> tailwindcss
curl -fsSL https://raw.githubusercontent.com/1REDfriend/web-nas/main/script.sh | bash
and setup your envorument , npm install && npx prisma generate , etc...
- step 1 : install
Node js, npm - step 2 : download this Web-nas to your favorite directory.
- step 3 : cd into,
npm install && npx prisma generate - step 4 :
sudo apt install libnss3-tools - step 5 : Edit your
.envFile - step 6 :
npm run devornpm run buildif you setup .env successful.
NOTE if error xterm your should install
sudo apt install build-essential -yfor complie "C" lang to type script.
noVNC NOTE if use x64 or AMD please config a docker compose file.
If you use cloudflare. You should Disable TLS on cloudflare zero trush
Run cloudflared on the Docker host and point the tunnel at https://localhost:5491. Caddy then takes the visitor's address from Cf-Connecting-IP, so login and API limits count each visitor on their own. It trusts that header only from Docker and loopback addresses (see CaddyFile); widen trusted_proxies there if cloudflared runs somewhere else.
DATABASE_URL="file:./main.sqlite" => Docker overrides this to /app/data/main.sqlite
TOKEN_COOKIE="your-token_cookie"
JWT_SECRET="your-jwt_secret"
STORAGE_ROOT="your-storage_root" => Docker overrides this to /host_root
STORAGE_INTERNAL="your-storage_internal" => default storage; Docker overrides this to /app/data/storage
TERMINAL_HOST="" => optional host of the web terminal, e.g. nas-ssh.example.com behind a tunnel; read at runtime, so no rebuild is needed; empty means <the host you opened>:7255
COOKIE_DOMAIN="example.com" => optional; set it when the terminal is on another subdomain (e.g. nas.example.com + nas-ssh.example.com) so the login cookie reaches both
PROTECTED_PATHS="" => optional, comma-separated absolute paths that no role (not even ADMIN) can touch from the web UI
STORAGE_WRITABLE_PATHS="" => optional, comma-separated absolute paths that are real disk; when set, uploads, new folders, renames, moves and deletes are refused anywhere else, and these folders themselves cannot be deleted. Docker sets it to the mounted NAS_HOST_DIR
Docker-only (read by docker-compose.yaml):
NAS_HOST_DIR="/mnt" => the only host folder the app can see, mounted at /host_root/mnt; folder assignments use paths like /mnt/...
PUID="1000" / PGID="1000" => host user the app runs as; it can only change files that user may change
WEBNAS_PORT="5491" / WEBSSH_PORT="7255" => host ports published by Caddy
NAS_DROPS_DIR="/mnt/drops" => host folder for drops, inside NAS_HOST_DIR; default NAS_HOST_DIR/drops. The PUID user must be able to create it (e.g. /mnt/sda1/drops when /mnt itself belongs to root)
API_KEY_RATE_LIMIT="120" => optional, requests per minute each API key may make (see API)
PUBLIC_URL="" => optional address people use to reach the app (e.g. https://nas.example.com), used for the links in API answers; empty = taken from the request
DROPS_DEFAULT_DIR="" => folder for drops until an admin picks another one; empty = <STORAGE_INTERNAL>/drops. Docker sets it from NAS_DROPS_DIR
DROPS_SWEEP_SECONDS="300" => how often expired drops are deleted
NOTE the web terminal (port 7255) is ADMIN-only: Caddy checks the file manager login before every request. Open it on the same host name as the file manager, or set
COOKIE_DOMAIN, otherwise the browser does not send the login cookie and the terminal answers 401.
mkdir -p data
docker compose up -d --build
The container syncs the database schema on start and keeps everything that must survive a rebuild in ./data (database and trash).
docker compose down
mkdir -p data
cp prisma/main.sqlite data/main.sqlite
sudo cp -a storage data/storage && sudo chown -R 1000:1000 data
docker compose up -d --build
Keep the old files until everything works. Folder assignments outside NAS_HOST_DIR show as Offline until that folder is mounted too.
Other programs and servers can store and fetch files through /api/v1 with an API key.
In the web UI open Settings → API keys (/settings/api-keys, ADMIN and USER accounts) and create a key. Choose:
- Permissions:
list(folders and file details),read(download),upload(upload and create folders),delete(move to trash),drop(share temporary files by link, see Drops) - Only this folder (optional): the key cannot reach anything outside it, symlinks included
- Largest upload and Expires after
The full key (wnas_<prefix>_<secret>) is shown once. Only its hash is stored; if you lose it, revoke it and create a new one. A key never gets more than its owner has: folder assignments and Protected Folders rules still apply.
Send it on every request:
Authorization: Bearer wnas_xxxxxxxx_...
Keys work only on /api/v1; every other route needs a browser login.
| Method | Path | Permission | Notes |
|---|---|---|---|
| GET | /api/v1/me |
– | the key's owner, permissions, folder and limits |
| GET | /api/v1/files?path=/folder |
list |
folder contents; page, limit (max 1000), sort=name|size|modified, order=asc|desc. Without path: the key's folder, or your assigned folders |
| GET | /api/v1/files/stat?path=/folder/file |
list |
one file or folder |
| GET | /api/v1/files/download?path=/folder/file |
read |
supports Range; a folder comes as a zip (not for folder-locked keys) |
| PUT | /api/v1/files/raw?path=/folder/file |
upload |
the body is the file; overwrite=1 to replace, mkdirs=0 to require the folder to exist |
| POST | /api/v1/files/upload |
upload |
multipart: file, path (folder), optional name, overwrite, mkdirs |
| POST | /api/v1/folders |
upload |
JSON { "path": "/folder/new" }, creates parents too |
| DELETE | /api/v1/files?path=/folder/file |
delete |
moves it to the owner's trash (restorable from the web UI) |
Drops (see below):
| Method | Path | Permission | Notes |
|---|---|---|---|
| PUT | /api/v1/drops/raw?name=report.pdf |
drop |
the body is the file; optional expiresIn (30m, 24h, 7d) and maxDownloads |
| POST | /api/v1/drops/upload |
drop |
multipart: file, optional name, expiresIn, maxDownloads |
| GET | /api/v1/drops |
drop |
your drops that can still be downloaded; ?id= for one |
| DELETE | /api/v1/drops?id=... |
drop |
stop sharing now; the file is deleted shortly after |
Responses are JSON: { "ok": true, "data": ... } or { "ok": false, "error": "...", "code": "..." }, except downloads. Codes: BAD_REQUEST, UNAUTHORIZED, INVALID_API_KEY, API_KEY_NOT_ALLOWED, PASSWORD_CHANGE_REQUIRED, FORBIDDEN, NOT_FOUND, CONFLICT, PAYLOAD_TOO_LARGE, RATE_LIMITED, INTERNAL.
KEY=wnas_xxxxxxxx_...
NAS=https://nas.example.com
curl -H "Authorization: Bearer $KEY" "$NAS/api/v1/me"
curl -H "Authorization: Bearer $KEY" "$NAS/api/v1/files?path=/mnt/backup"
curl -T report.pdf -H "Authorization: Bearer $KEY" "$NAS/api/v1/files/raw?path=/mnt/backup/report.pdf"
curl -F path=/mnt/backup -F file=@photo.jpg -H "Authorization: Bearer $KEY" "$NAS/api/v1/files/upload"
curl -o report.pdf -H "Authorization: Bearer $KEY" "$NAS/api/v1/files/download?path=/mnt/backup/report.pdf"
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"path":"/mnt/backup/2026"}' "$NAS/api/v1/folders"
curl -X DELETE -H "Authorization: Bearer $KEY" "$NAS/api/v1/files?path=/mnt/backup/report.pdf"
- Each key may make
API_KEY_RATE_LIMITrequests a minute (default 120); after that it gets429withRetry-After. - Uploads stop at the key's Largest upload (or the server cap of 100 GB) with
413; nothing is left behind. - Too many invalid keys from one address get
429for 15 minutes. Valid keys are never blocked by this. - Behind Cloudflare, a single request body is capped by your Cloudflare plan (100 MB on Free), whatever the key allows.
Uploads stream straight to disk; they do not pass through proxy.ts, which would hold the whole body in memory.
A drop is a file that anyone with its link can download until it expires, or until it has been downloaded maxDownloads times. Then it is deleted on its own.
curl -T report.pdf -H "Authorization: Bearer $KEY" "$NAS/api/v1/drops/raw?name=report.pdf&expiresIn=24h&maxDownloads=3"
The answer has url (a page to hand to people) and downloadUrl (for curl/wget), plus expiresAt. Lifetime starts when the upload finishes.
The page at url needs no login. It shows the name, size and time left, previews images, video and audio, and has a Download button. Every download is counted; a link that expired, ran out of downloads or was removed answers 404.
-
A drop without a download limit can be resumed (
Range) and previewed. -
A drop with a download limit is always sent whole and is never previewed, so nobody can fetch it without it being counted.
-
ADMIN sets the default lifetime (24 hours), the longest lifetime (7 days) and a quota per user (10 GB of live drops) in
PUT /api/admin/drops. -
Drops are kept in one drop folder,
NAS_HOST_DIR/dropswith Docker. An ADMIN can pick another one; the drops already stored move there in the background. The new folder must be empty, missing, or an old drop folder, and nobody can open it from the file manager. -
GUEST accounts cannot create drops.
npm run test:e2e runs the end-to-end suites against a throwaway server, database and storage tree; see tests/e2e/README.md. npm run test:unit runs the unit tests in tests/unit.
Admins can block actions (view, download, upload, rename, move, delete, share) on any folder per role from Settings → Protected folders (/settings/admin/protected-folders). Built-in rules protect OS folders (/etc, /usr, /var, ...) and make GUEST read-only; they can be edited, removed, or restored with Restore defaults.
The app folder and STORAGE_INTERNAL are always locked, and so is anything in PROTECTED_PATHS.
Without Docker, update the database after pulling: npx prisma db push (existing install) or npx prisma migrate deploy (fresh database).