A Wi-Fi hotspot file server that turns an Android phone into a full-featured web file manager. Browse, upload, download, edit, rename, and delete files on /sdcard from any device connected to the phone's hotspot — no client-side install needed.
| Category | Capabilities |
|---|---|
| Browse | Virtualised list that stays at 60 fps with 20,000+ files, sortable columns (name/size/date), breadcrumbs, quick-access chips and instant filtering |
| Preview | In-place glass sheet for images, video, audio, PDF and text — no download round-trip |
| Upload | Batched multi-file upload with live progress, folder upload, drag-and-drop, 100 MB per request, streamed to disk instead of buffered in RAM |
| Download | Single-click download with correct MIME type, RFC 5987 UTF-8 filenames, HTTP Range support (resume / video seeking) |
| Edit | Code editor in a glass sheet, Ctrl/Cmd + S, binary guard and 5 MB ceiling; unsaved-change guard with a custom dialog |
| Manage | Create files/folders, rename, move, multi-select with batch delete, copy path, styled confirmations |
| Navigation | URL + history sync (back/forward works), per-folder cache, keyboard shortcuts (/ search, U upload, N new folder, Ctrl+A select all) |
| i18n | English / 中文 switch with localStorage persistence; first visit follows the browser language |
| Theme | Liquid Glass dark / light themes, follows prefers-color-scheme, manual toggle with localStorage memory |
| Responsive | Desktop grid → mobile list with bottom sheets and an extended FAB at 820px |
| Security | Path traversal protection, POST size limits, CSRF origin check, admin sessions with bearer tokens |
| Concurrency | Threaded request handling; gzip for HTML and large JSON listings; ETag/304 for the UI |
- Listings use a single
os.scandirpass and never open files to guess "is this text?". - Admin mode reads the filesystem directly and only falls back to
suon a real permission error. - Uploads are spooled to a temp file and memory-mapped, so a 100 MB upload does not require 200 MB of RAM.
- The file list only renders visible rows; 20,000 files ≈ 20 DOM nodes.
| File | Purpose |
|---|---|
dist/webserver.tar.gz |
Standalone: server.py + launch.sh + stop.sh + READMEs |
dist/filebase-v6.0.0.zip |
Magisk / KernelSU / APatch flashable module (includes WebUI panel + log management) |
- Root access (
su) — required for iptables + auxiliary IP; optional if running directly - Python 3.8+ — install via Termux (
pkg install python) or a Magisk-bundled binary - Mobile hotspot turned on (for hotspot mode; Wi‑Fi LAN also works)
- Any web browser — phone, tablet, laptop — connected to the phone's hotspot / Wi‑Fi
adb push server.py launch.sh stop.sh /data/local/tmp/
adb shell chmod +x /data/local/tmp/launch.sh /data/local/tmp/stop.shSettings → Hotspot & tethering → Wi‑Fi hotspot.
su
sh /data/local/tmp/launch.shThe launcher auto‑detects the hotspot interface, picks a random auxiliary IP (e.g. 192.168.43.172), sets up iptables, and starts the server:
╔══════════════════════════════════════════════════════════╗
║ Server running! Connect to: ║
║ http://192.168.43.172:6532 ║
║ http://192.168.43.172 (port 80 → 6532) ║
║ Serving: /sdcard ║
║ Log level: info Log file: logs/server.log ║
╚══════════════════════════════════════════════════════════╝
sh /data/local/tmp/stop.shpython3 /data/local/tmp/server.py
# Access at http://<phone-ip>:6532The module provides a persistent system-level install with one‑tap control via your root manager's Action button and an optional graphical WebUI panel. The server does not start on boot — you start and stop it explicitly (Action button, WebUI, or the CLI). This avoids the classic "hotspot is not up yet at boot, so the random-IP setup fails" problem.
- Download
dist/filebase-v6.0.0.zip - Root manager → Modules → Install from storage → select the zip
- Reboot once. KernelSU/Magisk stage new modules in
modules_update/and merge them in on the next boot, soaction.shandwebroot/only exist after a reboot — the WebUI is refused and the Action button fails until then. - After rebooting, use the Action button or open the WebUI
Supported managers: Magisk 28.0+, KernelSU 1.0.2+, APatch (latest)
| Interface | How |
|---|---|
| Action button | Tap "Action" in the root manager module list → starts the server, or stops it if it is already running (action.sh toggle; start/stop/restart/status/log also work) |
| WebUI panel | Open it from the KernelSU Manager module page (the manager renders webroot/ in a WebView). On Magisk / APatch use KSUWebUIStandalone or MMRL |
| Terminal | su -c 'sh /data/adb/modules/filebase/action.sh start' |
sh action.sh start Start server (auto-detect hotspot, random IP, iptables)
sh action.sh stop Stop server + clean up alias IP + iptables
sh action.sh restart Stop then start
sh action.sh toggle Start if stopped, stop if running
sh action.sh status Show PID, bind IP, interface, log level, connectivity
sh action.sh info Machine-readable KEY=VALUE status (used by the WebUI)
sh action.sh log [N] Show last N log lines (default 50)
sh action.sh loglevel [LVL] Read (no arg) or set the log level
KernelSU runs the Action button as ksud module action <id> without any
arguments, so the no-argument default is toggle: tapping Action starts the
server when it is stopped and stops it when it is running. (It used to default
to status, which is why the button only ever printed "Server STOPPED".)
The panel is written the way KernelSU's module WebUI guide describes:
- Entry point is
webroot/index.html; CSS/JS live next to it. Permissions and SELinux context are left to KernelSU, as the guide requires. - Shell access uses the official
kernelsulibrary —const { errno, stdout, stderr } = await exec(cmd). The library is vendored verbatim aswebroot/kernelsu.js(Apache‑2.0) so the page canimportit without a bundler. The old synchronousksu.exec(cmd)string API is not the documented interface and broke under other WebUI hosts. - Module directory comes from
moduleInfo()instead of a hard‑coded path. - Native toasts via the library's
toast(). - Start / Stop / Restart buttons with a timeout guard.
- Live status (PID, address, interface, log level) parsed from
action.sh info, a stableKEY=VALUEcontract — not scraped from human‑readable output. - Log level selector:
Info·Error·Debug·Off. - i18n: English / 中文, first visit follows the browser language.
- Console shows the complete output. KernelSU runs
exec()in a temporary root shell that is destroyed when the command returns, so a command's stdout can come back empty or truncated (and a naivecmd &server is killed with it). FileBase therefore starts the server withsetsidand hasaction.shpersist its full output tologs/action.log; the WebUI reads that file back. After a start/restart it also appendslogs/server.log, which holds the bind address and any Python traceback. - The address is always discoverable. The server writes
PID/HOST/PORT/IFACE/IPto a runtime status file, so the panel can show the URL even when the server listens on0.0.0.0or the log level isoff.
Following the module config guide, the value is persisted with KernelSU's module config system:
ksud module config set log_level debug # KSU_MODULE is set by action.shOn Magisk / APatch (no ksud) it falls back to logs/.config. action.sh
reads the ksud config first, then the file, and both are written whenever the
level changes — so the setting survives manager uninstalls on KernelSU and
still works everywhere else.
| Level | Behavior |
|---|---|
info (default) |
Client IP + HTTP method + status code |
error |
Only 4xx / 5xx responses |
debug |
Timestamp, IP:port, method, full path |
off |
No log file (stdout → /dev/null) |
Logs are written to <module>/logs/server.log. Set the level with the WebUI dropdown, sh action.sh loglevel <level>, or ksud module config set log_level <level> (see above). A new level takes effect the next time the server starts — use Restart in the WebUI to apply it immediately.
sh "Magisk Module/build.sh"The script syncs server.py / launch.sh / stop.sh from the repo root into
Magisk Module/common/, then writes dist/filebase-<version>.zip plus a fresh
dist/webserver.tar.gz. The release workflow uses the same script, so a
release can never bundle a stale copy of the server.
All endpoints prefixed with /api.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/list?path=<path> |
List directory contents (add compact=1 for positional arrays — ~60% smaller for huge folders) |
GET |
/api/download?path=<path> |
Download a file (RFC 5987 filename, HTTP Range supported; inline=1 for previews) |
POST |
/api/upload |
Upload file(s) — multipart/form-data |
POST |
/api/write |
Create / overwrite a file — JSON {path, content, overwrite?} (overwrite:false returns 409 if the path exists) |
POST |
/api/delete |
Delete a file or directory — JSON {path} or {paths: [...]} for a batch |
POST |
/api/mkdir |
Create a directory — JSON {path} |
POST |
/api/rename |
Rename / move — JSON {oldPath, newPath} |
POST |
/api/login |
Admin login — JSON {user, pass} → {ok, token} |
POST |
/api/logout |
Admin logout — invalidates token |
GET |
/api/auth |
Check admin auth status → {admin: bool} |
GET |
/api/text?path=<path> |
Read a text file for the editor (415 for binary, 413 above 5 MB) |
GET |
/api/stats |
Free/used storage for the active root |
Downloads honour a single-range Range: bytes=... request (206 partial
content, 416 when unsatisfiable) so large files can be resumed. Admin sessions
normally authenticate with a Bearer header; native browser downloads may
instead append &token=<token> to the URL because <a download> cannot send
headers.
Success (/api/list):
{ "entries": [{ "name": "foo.txt", "isdir": false, "size": 1234, "mtime": 1700000000, "editable": true }] }Success (other POST):
{ "ok": true }Error:
{ "error": "description" }server.py HTTP server + embedded SPA frontend
launch.sh Root launcher (interface scan, iptables, random IP)
stop.sh Graceful shutdown (alias removal, iptables cleanup)
README.md / README_CN.md Documentation (EN / ZH)
magisk_module/ Magisk/KSU/APatch module source
├── module.prop Multi-root metadata (ksu=1, sufs=1)
├── customize.sh Install script
├── action.sh Control entry-point (start/stop/status/log)
├── uninstall.sh Cleanup
├── common/ Server files → copied to module root on install
│ ├── server.py
│ ├── launch.sh
│ └── stop.sh
├── webroot/
│ ├── index.html WebUI entry point (required by the KSU guide)
│ ├── script.js WebUI logic (ES module, imports kernelsu.js)
│ ├── kernelsu.js Official KernelSU WebUI library (vendored)
│ └── style.css WebUI styles
├── META-INF/ Recovery flash support
└── build.sh Rebuild the flashable zip + standalone tarball
dist/ Distribution archives
├── webserver.tar.gz Standalone package
└── filebase-v6.0.0.zip Flashable module
Design language — Liquid Glass × Material 3 Expressive
- Glass surfaces: layered translucency, 1px specular rims, inner highlights and a subtle noise layer; gradient orbs instead of blur filters keep the effect cheap on phones
- M3 Expressive: large radii, tonal colour blocks, spring motion (
cubic-bezier(.22,1.2,.36,1)), extended FAB, bottom sheets and an M3 navigation bar in the WebUI - Typography: system font stack only — no external webfont requests, fully offline
- Theme: CSS custom properties with
[data-theme="dark"]/[data-theme="light"], view transitions on toggle where supported - Motion budget: transform/opacity-only animation, virtualised rows,
prefers-reduced-motionandprefers-reduced-transparencysupport - i18n: Template-driven via
data-i18nattributes,localStoragepersistence - Security: File actions use
data-*attributes + event delegation; no inlineonclick - Upload: Visually‑hidden native file input (opacity:0) for cross‑browser compatibility
FileBase includes a hidden super admin mode for full root-level file management.
- An Admin button is visible in the top-right control bar of the web UI
- Click it to open the login dialog
- The Admin button remains visible at all times
| Field | Value |
|---|---|
| Username | admin |
| Password | hotsteel |
- File system root shifts from
/sdcardto/— the entire Android filesystem is accessible - Navigate up from
/sdcardto the root directory and browse/data,/system,/proc, etc. - All operations (upload, delete, rename, edit) work on any path the server process can access
- The footer shows
Serving: /instead ofServing: /sdcard - A red ADMIN badge appears in the UI
- The login endpoint uses a fixed username/password — change
ADMIN_USERandADMIN_PASSinserver.pyfor production use
| Problem | Solution |
|---|---|
Python not found |
Install via Termux (pkg install python3) or push a binary |
No hotspot IP detected |
Ensure hotspot is enabled before launching; module scans all interfaces |
| Upload button doesn't open file picker | Fixed — file input uses opacity-based hiding, not display:none |
| Theme toggle not working | Fixed — SVG icon wrapper uses <span> for reliable innerHTML |
| Chinese filenames fail on download | Fixed — RFC 5987 filename*=UTF-8''... encoding |
| Module WebUI hangs on Start | Fixed — backgrounded command + 15s timeout |
| Can't access from client | Verify client is on phone's hotspot Wi‑Fi, not mobile data |
grep: Unknown option |
Fixed — uses sed fallback for Android busybox compatibility |
error log level shows nothing |
Fixed — the 4xx/5xx filter now parses the status code correctly |
Admin download of /data files fails |
Fixed — admin downloads carry the token, and large files stream instead of buffering |
| One big download freezes the whole page | Fixed — the server is now threaded |
| Cannot resume a large download | Fixed — HTTP Range requests are supported |
| WebUI shows "API unavailable" | The page was opened outside a WebUI host — open it from the KernelSU Manager (or KSUWebUIStandalone / MMRL); the terminal fallback is shown on the page |
| WebUI controls do nothing | The panel needs the kernelsu WebUI bridge; check that the module is enabled and rebooted after install |
| Action button / WebUI fails right after installing | Reboot once first. New modules are staged in modules_update/ and merged in at boot, so action.sh and webroot/ do not exist until then |
| Do I need a metamodule? | No. FileBase never modifies /system, so KernelSU's metamodule requirement (which only applies to modules mounting a system/ directory) does not apply |
| No logs / can't find the IP and port | Check logs/action.log (complete output of the last command) and logs/server.log (server's own output). The WebUI console shows both. The runtime status file also always carries the address |
| Switching the log level seems to do nothing | A new level is applied the next time the server starts — tap Restart (or run action.sh restart). While off, logs/server.log is intentionally emptied |
| Server dies right after starting from the panel | Fixed — it is launched with setsid so it survives the manager's temporary root shell |
MIT