filemgr 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- filemgr-0.1.0/LICENSE +21 -0
- filemgr-0.1.0/PKG-INFO +334 -0
- filemgr-0.1.0/README.md +302 -0
- filemgr-0.1.0/pyproject.toml +59 -0
- filemgr-0.1.0/setup.cfg +4 -0
- filemgr-0.1.0/src/filemgr/__init__.py +3 -0
- filemgr-0.1.0/src/filemgr/app.py +679 -0
- filemgr-0.1.0/src/filemgr/cli.py +175 -0
- filemgr-0.1.0/src/filemgr/helper.py +929 -0
- filemgr-0.1.0/src/filemgr/static/app.css +1064 -0
- filemgr-0.1.0/src/filemgr/static/app.js +2121 -0
- filemgr-0.1.0/src/filemgr/static/i18n.js +488 -0
- filemgr-0.1.0/src/filemgr/static/index.html +402 -0
- filemgr-0.1.0/src/filemgr/templates/config.toml.example +22 -0
- filemgr-0.1.0/src/filemgr/templates/filemgr.service +26 -0
- filemgr-0.1.0/src/filemgr.egg-info/PKG-INFO +334 -0
- filemgr-0.1.0/src/filemgr.egg-info/SOURCES.txt +19 -0
- filemgr-0.1.0/src/filemgr.egg-info/dependency_links.txt +1 -0
- filemgr-0.1.0/src/filemgr.egg-info/entry_points.txt +2 -0
- filemgr-0.1.0/src/filemgr.egg-info/requires.txt +5 -0
- filemgr-0.1.0/src/filemgr.egg-info/top_level.txt +1 -0
filemgr-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 filemgr contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
filemgr-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: filemgr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Web file manager for Linux with PAM auth, per-user privilege isolation via setuid, and bioinformatics-friendly previews
|
|
5
|
+
Author: Lings01
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Lings01/filemgr
|
|
8
|
+
Project-URL: Repository, https://github.com/Lings01/filemgr
|
|
9
|
+
Project-URL: Issues, https://github.com/Lings01/filemgr/issues
|
|
10
|
+
Keywords: file-manager,pam,bioinformatics,fastapi,multi-user,self-hosted,setuid,linux,fastq
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Framework :: FastAPI
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
21
|
+
Classifier: Topic :: System :: Filesystems
|
|
22
|
+
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: fastapi>=0.110
|
|
27
|
+
Requires-Dist: uvicorn[standard]>=0.27
|
|
28
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
29
|
+
Requires-Dist: python-pam>=2.0
|
|
30
|
+
Requires-Dist: six>=1.16
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# filemgr
|
|
34
|
+
|
|
35
|
+
A lightweight, multi-user web file manager for Linux servers with **real PAM
|
|
36
|
+
authentication and per-user privilege isolation**. Each login performs
|
|
37
|
+
filesystem operations through a `setuid`-dropped child process, so access
|
|
38
|
+
control is enforced by the kernel — two users using the same tool at the same
|
|
39
|
+
time cannot see or touch each other's files unless the filesystem permissions
|
|
40
|
+
allow it.
|
|
41
|
+
|
|
42
|
+
Extra batteries for **bioinformatics workflows**: recognizes FASTQ / BAM /
|
|
43
|
+
VCF / GFF / BED / H5AD / RData / ipynb / SIF and more, with transparent
|
|
44
|
+
`.gz` preview.
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- **PAM login** against real system accounts (with a configurable whitelist)
|
|
49
|
+
- **Multi-user isolation** via `setuid` per request (service runs as root, work
|
|
50
|
+
happens as the logged-in user's uid)
|
|
51
|
+
- **Browse, upload, download, rename, delete, mkdir** with keyboard shortcuts,
|
|
52
|
+
drag-and-drop, and right-click context menus
|
|
53
|
+
- **Sortable list** with sticky headers and `aria-sort`; sort by name / size /
|
|
54
|
+
modified time
|
|
55
|
+
- **Fuzzy search** (VS Code-style subsequence scoring) — instant local filter
|
|
56
|
+
in the current directory, Enter triggers a recursive global search from the
|
|
57
|
+
home root with match highlighting
|
|
58
|
+
- **Previews** for text, code, images, video, audio, PDF; touchpad pinch and
|
|
59
|
+
keyboard zoom for images; transparent gunzip preview for `.fastq.gz`,
|
|
60
|
+
`.vcf.gz`, `.fa.gz`, `.bed.gz`, and other compressed text files
|
|
61
|
+
- **Folder sizes** computed recursively with a timeout + file-count cap to
|
|
62
|
+
avoid runaway scans
|
|
63
|
+
- **Statistics panel** at the top of the page: total usage, file count, folder
|
|
64
|
+
count, recent-7-days modifications, breakdown by type (with bio-aware
|
|
65
|
+
categories), top-N largest files, most-recently-modified files; click any
|
|
66
|
+
type to see top-N files of that type with an adjustable N
|
|
67
|
+
- **Recycle bin**: delete is a soft move to `~/.filemgr-trash`, toast shows an
|
|
68
|
+
"Undo" button, a recycle-bin modal lists entries with remaining-time
|
|
69
|
+
indicator, and items older than `trash_retention_days` (default 3) are
|
|
70
|
+
auto-purged
|
|
71
|
+
- **Transfer panel** with progress bar, live speed, ETA, and cancel button
|
|
72
|
+
(for uploads and downloads)
|
|
73
|
+
- **HTTP Range** support for video/audio scrubbing and resumable downloads
|
|
74
|
+
- **URL deep linking** — current directory and search state live in the URL
|
|
75
|
+
hash, so refresh / share / back-button all just work
|
|
76
|
+
- **Accessibility**: `aria-label` on icon buttons, `focus-visible` outlines,
|
|
77
|
+
`aria-live="polite"` toasts, keyboard navigation on rows
|
|
78
|
+
(Enter / Space / Delete / F2 / Shift+F10 / ContextMenu), and
|
|
79
|
+
`prefers-reduced-motion` support
|
|
80
|
+
- **Dark mode**: follows `prefers-color-scheme` by default, with a manual
|
|
81
|
+
toggle in the top bar (persisted in `localStorage`)
|
|
82
|
+
- **i18n**: ships with English and Simplified Chinese; defaults to the
|
|
83
|
+
browser's `navigator.language`, switch via the `EN/中` top-bar button
|
|
84
|
+
(persisted in `localStorage`)
|
|
85
|
+
- **List virtualization** kicks in automatically above 500 files per directory
|
|
86
|
+
so huge home dirs stay smooth
|
|
87
|
+
|
|
88
|
+
## Screenshots
|
|
89
|
+
|
|
90
|
+
<table>
|
|
91
|
+
<tr>
|
|
92
|
+
<td width="50%"><img src="docs/screenshots/main-en.png" alt="Main view (English)"><br/><sub><b>English UI (dark theme)</b> — stats panel up top, sortable file list below. Switch via the EN/中 button in the top bar.</sub></td>
|
|
93
|
+
<td width="50%"><img src="docs/screenshots/main-zh.png" alt="Main view (中文)"><br/><sub><b>中文 UI</b> — same view, different language. Preference persists in localStorage.</sub></td>
|
|
94
|
+
</tr>
|
|
95
|
+
<tr>
|
|
96
|
+
<td width="50%"><img src="docs/screenshots/search.png" alt="Global fuzzy search"><br/><sub><b>Fuzzy search</b> — subsequence matching with score-ranked results and match highlighting.</sub></td>
|
|
97
|
+
<td width="50%"><img src="docs/screenshots/top-by-type.png" alt="Top-N by file type"><br/><sub><b>Top-N by type</b> — click any category in the stats panel to see its largest files; N is adjustable.</sub></td>
|
|
98
|
+
</tr>
|
|
99
|
+
<tr>
|
|
100
|
+
<td width="50%"><img src="docs/screenshots/preview-text.png" alt="In-browser text preview"><br/><sub><b>Text preview</b> — line numbers, word-wrap toggle, 1 MB cap; transparent <code>.gz</code> decompression for bio formats.</sub></td>
|
|
101
|
+
<td width="50%"><img src="docs/screenshots/login.png" alt="Login page"><br/><sub><b>Login</b> — PAM auth against real system accounts on a whitelist.</sub></td>
|
|
102
|
+
</tr>
|
|
103
|
+
</table>
|
|
104
|
+
|
|
105
|
+
## How it works
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
┌───────────────────────────────┐ ┌────────────────────────────┐
|
|
109
|
+
│ Browser │ HTTPS │ uvicorn + FastAPI (root) │
|
|
110
|
+
│ - cookie session │◀──────▶│ - PAM auth │
|
|
111
|
+
│ - no mutation of fs directly │ │ - per-request helper.py │
|
|
112
|
+
└───────────────────────────────┘ │ subprocess: │
|
|
113
|
+
│ initgroups/setgid/setuid│
|
|
114
|
+
│ to the logged-in user │
|
|
115
|
+
│ → os.scandir/open/... │
|
|
116
|
+
└──────────┬─────────────────┘
|
|
117
|
+
│ kernel-enforced
|
|
118
|
+
▼ permissions
|
|
119
|
+
/home/data/zrx/...
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- `app.py` runs as **root** (via systemd) so it can drop privileges for each
|
|
123
|
+
request.
|
|
124
|
+
- Every filesystem operation is executed by spawning `helper.py` with args
|
|
125
|
+
`--uid --gid --home …` and a sub-command (`list`, `stat`, `dirsize`, `read_stream`,
|
|
126
|
+
`write_stream`, `mkdir`, `rename`, `delete`, `search`, `stats`, `top_by_type`,
|
|
127
|
+
`trash_*`). The helper **drops privileges immediately** via
|
|
128
|
+
`os.setgroups([]) / os.initgroups / os.setgid / os.setuid`, then does the work.
|
|
129
|
+
- A small per-session `stat` cache plus HTTP ETag / `Cache-Control` on media
|
|
130
|
+
previews keeps typical browsing snappy without sacrificing isolation.
|
|
131
|
+
|
|
132
|
+
## Prerequisites
|
|
133
|
+
|
|
134
|
+
- Linux with `systemd`
|
|
135
|
+
- Python 3.11+ (uses `tomllib`; tested on 3.12)
|
|
136
|
+
- `libpam0g-dev` headers (Debian/Ubuntu) to build the `python-pam` wheel
|
|
137
|
+
- A PAM service on the host (usually already present — `login`,
|
|
138
|
+
`common-auth`, `passwd`, and `sshd` are auto-tried as fallbacks)
|
|
139
|
+
- Root on the host (the service and `setuid` require it)
|
|
140
|
+
|
|
141
|
+
## Install & run
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# 1. Install. A virtualenv is strongly recommended so pip doesn't fight
|
|
145
|
+
# Debian's PEP 668 protection.
|
|
146
|
+
python3 -m venv /opt/filemgr-venv
|
|
147
|
+
/opt/filemgr-venv/bin/pip install git+https://github.com/Lings01/filemgr.git
|
|
148
|
+
|
|
149
|
+
# 2. Generate a config and edit the [[users]] whitelist.
|
|
150
|
+
sudo mkdir -p /etc/filemgr
|
|
151
|
+
sudo /opt/filemgr-venv/bin/filemgr init-config /etc/filemgr/config.toml
|
|
152
|
+
sudo $EDITOR /etc/filemgr/config.toml
|
|
153
|
+
|
|
154
|
+
# 3. Install and start the systemd unit.
|
|
155
|
+
sudo /opt/filemgr-venv/bin/filemgr install-service --config /etc/filemgr/config.toml
|
|
156
|
+
sudo systemctl enable --now filemgr
|
|
157
|
+
filemgr status
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Or, to try it quickly without systemd (needs sudo for real setuid):
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
sudo /opt/filemgr-venv/bin/filemgr run --config /etc/filemgr/config.toml
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The `filemgr` CLI exposes:
|
|
167
|
+
|
|
168
|
+
| Command | What it does |
|
|
169
|
+
|-------------------------------|------------------------------------------------------------|
|
|
170
|
+
| `filemgr run` | Run the server in the foreground |
|
|
171
|
+
| `filemgr init-config [PATH]` | Write a sample config.toml to `PATH` (default `./config.toml`) |
|
|
172
|
+
| `filemgr install-service` | Generate and install the systemd unit (needs root) |
|
|
173
|
+
| `filemgr uninstall-service` | Remove the systemd unit |
|
|
174
|
+
| `filemgr status` | `systemctl status filemgr` |
|
|
175
|
+
| `filemgr logs [-f] [-n N]` | `journalctl -u filemgr` |
|
|
176
|
+
| `filemgr version` | Print the version |
|
|
177
|
+
|
|
178
|
+
Config file is discovered in this order: `--config` → `$FILEMGR_CONFIG` →
|
|
179
|
+
`./config.toml` → `~/.config/filemgr/config.toml` → `/etc/filemgr/config.toml`.
|
|
180
|
+
|
|
181
|
+
Open `http://127.0.0.1:8765` (or your configured `listen_host:port`) in a
|
|
182
|
+
browser. For access from another machine, either change `listen_host` to
|
|
183
|
+
`0.0.0.0` and open a firewall port, or use an SSH tunnel:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
ssh -L 8765:127.0.0.1:8765 you@server
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
For production, put nginx/Caddy in front for TLS. A sample nginx block is
|
|
190
|
+
included at the end of this README.
|
|
191
|
+
|
|
192
|
+
### Development install
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
git clone https://github.com/Lings01/filemgr.git
|
|
196
|
+
cd filemgr
|
|
197
|
+
python3 -m venv venv
|
|
198
|
+
./venv/bin/pip install -e .
|
|
199
|
+
./venv/bin/filemgr init-config ./config.toml
|
|
200
|
+
./venv/bin/filemgr run
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Configuration (`config.toml`)
|
|
204
|
+
|
|
205
|
+
```toml
|
|
206
|
+
listen_host = "127.0.0.1"
|
|
207
|
+
listen_port = 8765
|
|
208
|
+
session_ttl_seconds = 28800
|
|
209
|
+
max_upload_bytes = 10_737_418_240
|
|
210
|
+
dirsize_max_files = 100_000
|
|
211
|
+
dirsize_timeout_seconds = 30
|
|
212
|
+
preview_text_max_bytes = 1_048_576
|
|
213
|
+
pam_service = "login"
|
|
214
|
+
trash_retention_days = 3
|
|
215
|
+
|
|
216
|
+
[[users]]
|
|
217
|
+
name = "alice"
|
|
218
|
+
root = "/home/alice"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- `pam_service`: the PAM service name used for authentication. If the primary
|
|
222
|
+
value fails, `common-auth`, `passwd`, and `sshd` are tried as fallbacks.
|
|
223
|
+
- `[[users]]`: repeatable block. `name` must exist as a system account; `root`
|
|
224
|
+
is the starting directory a user sees after login (defaults to their
|
|
225
|
+
passwd-entry home).
|
|
226
|
+
|
|
227
|
+
## Keyboard shortcuts
|
|
228
|
+
|
|
229
|
+
| Key | Action |
|
|
230
|
+
|---------------------------|------------------------------------------------|
|
|
231
|
+
| `/` or `Ctrl+F` | Focus the search box |
|
|
232
|
+
| `Enter` (in search) | Run global recursive search from home |
|
|
233
|
+
| `Esc` (in search) | Clear search, return to the current directory |
|
|
234
|
+
| `R` | Refresh the current directory |
|
|
235
|
+
| `Backspace` | Go up one level |
|
|
236
|
+
| `Enter` (on a row) | Open: enter folder / preview file |
|
|
237
|
+
| `Space` (on a row) | Toggle selection |
|
|
238
|
+
| `Delete` | Move selected to recycle bin |
|
|
239
|
+
| `F2` | Rename the selected item |
|
|
240
|
+
| `Shift+F10` / ContextMenu | Open the row action menu |
|
|
241
|
+
| `Ctrl+wheel` / pinch | Zoom (image preview; also works in PDF viewer) |
|
|
242
|
+
| `+` / `-` / `0` / `1` | Image preview: zoom in / out / fit / 1:1 |
|
|
243
|
+
|
|
244
|
+
## Security model
|
|
245
|
+
|
|
246
|
+
1. **Authentication** is delegated to PAM via `python-pam`. Failed logins pay a
|
|
247
|
+
fixed 1-second penalty to resist brute force. Only accounts listed in
|
|
248
|
+
`[[users]]` can authenticate, even if PAM would accept other users.
|
|
249
|
+
2. **Authorization** is enforced by the kernel. `app.py` forks a helper
|
|
250
|
+
subprocess and the helper calls `setgroups([]) / initgroups / setgid /
|
|
251
|
+
setuid` to the logged-in uid/gid **before touching the filesystem**. Read,
|
|
252
|
+
write, and traversal permissions are then whatever Linux says they are.
|
|
253
|
+
3. **Path validation** in the helper rejects paths that escape the configured
|
|
254
|
+
home root (via symlinks or `..`). This is a UX guard; the kernel is still
|
|
255
|
+
the ground truth.
|
|
256
|
+
4. **Session tokens** are 256-bit `secrets.token_urlsafe`, stored server-side
|
|
257
|
+
in memory. Cookies are `HttpOnly` + `SameSite=Strict`.
|
|
258
|
+
5. **The service must run as root.** This is required to drop privileges per
|
|
259
|
+
request. Running as a non-root user means filesystem operations fail.
|
|
260
|
+
|
|
261
|
+
## Recycle bin
|
|
262
|
+
|
|
263
|
+
Soft deletes move the file or folder to `~/.filemgr-trash/{id}__{name}` and
|
|
264
|
+
write a sidecar JSON under `~/.filemgr-trash/.meta/{id}.json` with the
|
|
265
|
+
original path and deletion timestamp.
|
|
266
|
+
|
|
267
|
+
- The toast after a successful delete shows an "Undo" button for 7 seconds.
|
|
268
|
+
- A recycle-bin modal (top-bar button) lists every entry, with remaining time
|
|
269
|
+
before auto-purge, per-item "Restore" / "Permanently delete", and an
|
|
270
|
+
"Empty recycle bin" button.
|
|
271
|
+
- Auto-purge happens opportunistically on every `trash_list` call and on
|
|
272
|
+
every `delete` call — no cron / systemd timer needed.
|
|
273
|
+
|
|
274
|
+
## Project layout
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
filemgr/
|
|
278
|
+
├── pyproject.toml Package + entry point definition
|
|
279
|
+
├── README.md
|
|
280
|
+
├── LICENSE
|
|
281
|
+
├── src/
|
|
282
|
+
│ └── filemgr/
|
|
283
|
+
│ ├── __init__.py
|
|
284
|
+
│ ├── app.py FastAPI app: endpoints, session, PAM, helper RPC
|
|
285
|
+
│ ├── helper.py Setuid child process; all filesystem ops live here
|
|
286
|
+
│ ├── cli.py `filemgr` CLI entry point
|
|
287
|
+
│ ├── static/ Single-page frontend (vanilla HTML/CSS/JS)
|
|
288
|
+
│ │ ├── index.html
|
|
289
|
+
│ │ ├── app.js
|
|
290
|
+
│ │ └── app.css
|
|
291
|
+
│ └── templates/
|
|
292
|
+
│ ├── config.toml.example
|
|
293
|
+
│ └── filemgr.service
|
|
294
|
+
└── docs/screenshots/
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Optional: nginx reverse proxy (HTTPS)
|
|
298
|
+
|
|
299
|
+
```nginx
|
|
300
|
+
server {
|
|
301
|
+
listen 443 ssl http2;
|
|
302
|
+
server_name files.example.com;
|
|
303
|
+
|
|
304
|
+
ssl_certificate /etc/letsencrypt/live/files.example.com/fullchain.pem;
|
|
305
|
+
ssl_certificate_key /etc/letsencrypt/live/files.example.com/privkey.pem;
|
|
306
|
+
|
|
307
|
+
client_max_body_size 10G;
|
|
308
|
+
proxy_request_buffering off; # stream uploads
|
|
309
|
+
proxy_buffering off; # stream downloads
|
|
310
|
+
proxy_read_timeout 3600s;
|
|
311
|
+
proxy_send_timeout 3600s;
|
|
312
|
+
|
|
313
|
+
location / {
|
|
314
|
+
proxy_pass http://127.0.0.1:8765;
|
|
315
|
+
proxy_set_header Host $host;
|
|
316
|
+
proxy_set_header X-Forwarded-For $remote_addr;
|
|
317
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Non-goals
|
|
323
|
+
|
|
324
|
+
- Not a cloud storage service: no federation, no external auth, no sharing
|
|
325
|
+
links to strangers.
|
|
326
|
+
- Not a full OS file manager replacement: no desktop icons, no mount
|
|
327
|
+
management, no permission editor (use your shell).
|
|
328
|
+
- Not hardened against a malicious service operator: the service runs as root,
|
|
329
|
+
so whoever controls the box controls the files anyway. The guarantees here
|
|
330
|
+
are between end users of the same box, not against root.
|
|
331
|
+
|
|
332
|
+
## License
|
|
333
|
+
|
|
334
|
+
MIT — see [LICENSE](LICENSE).
|
filemgr-0.1.0/README.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# filemgr
|
|
2
|
+
|
|
3
|
+
A lightweight, multi-user web file manager for Linux servers with **real PAM
|
|
4
|
+
authentication and per-user privilege isolation**. Each login performs
|
|
5
|
+
filesystem operations through a `setuid`-dropped child process, so access
|
|
6
|
+
control is enforced by the kernel — two users using the same tool at the same
|
|
7
|
+
time cannot see or touch each other's files unless the filesystem permissions
|
|
8
|
+
allow it.
|
|
9
|
+
|
|
10
|
+
Extra batteries for **bioinformatics workflows**: recognizes FASTQ / BAM /
|
|
11
|
+
VCF / GFF / BED / H5AD / RData / ipynb / SIF and more, with transparent
|
|
12
|
+
`.gz` preview.
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **PAM login** against real system accounts (with a configurable whitelist)
|
|
17
|
+
- **Multi-user isolation** via `setuid` per request (service runs as root, work
|
|
18
|
+
happens as the logged-in user's uid)
|
|
19
|
+
- **Browse, upload, download, rename, delete, mkdir** with keyboard shortcuts,
|
|
20
|
+
drag-and-drop, and right-click context menus
|
|
21
|
+
- **Sortable list** with sticky headers and `aria-sort`; sort by name / size /
|
|
22
|
+
modified time
|
|
23
|
+
- **Fuzzy search** (VS Code-style subsequence scoring) — instant local filter
|
|
24
|
+
in the current directory, Enter triggers a recursive global search from the
|
|
25
|
+
home root with match highlighting
|
|
26
|
+
- **Previews** for text, code, images, video, audio, PDF; touchpad pinch and
|
|
27
|
+
keyboard zoom for images; transparent gunzip preview for `.fastq.gz`,
|
|
28
|
+
`.vcf.gz`, `.fa.gz`, `.bed.gz`, and other compressed text files
|
|
29
|
+
- **Folder sizes** computed recursively with a timeout + file-count cap to
|
|
30
|
+
avoid runaway scans
|
|
31
|
+
- **Statistics panel** at the top of the page: total usage, file count, folder
|
|
32
|
+
count, recent-7-days modifications, breakdown by type (with bio-aware
|
|
33
|
+
categories), top-N largest files, most-recently-modified files; click any
|
|
34
|
+
type to see top-N files of that type with an adjustable N
|
|
35
|
+
- **Recycle bin**: delete is a soft move to `~/.filemgr-trash`, toast shows an
|
|
36
|
+
"Undo" button, a recycle-bin modal lists entries with remaining-time
|
|
37
|
+
indicator, and items older than `trash_retention_days` (default 3) are
|
|
38
|
+
auto-purged
|
|
39
|
+
- **Transfer panel** with progress bar, live speed, ETA, and cancel button
|
|
40
|
+
(for uploads and downloads)
|
|
41
|
+
- **HTTP Range** support for video/audio scrubbing and resumable downloads
|
|
42
|
+
- **URL deep linking** — current directory and search state live in the URL
|
|
43
|
+
hash, so refresh / share / back-button all just work
|
|
44
|
+
- **Accessibility**: `aria-label` on icon buttons, `focus-visible` outlines,
|
|
45
|
+
`aria-live="polite"` toasts, keyboard navigation on rows
|
|
46
|
+
(Enter / Space / Delete / F2 / Shift+F10 / ContextMenu), and
|
|
47
|
+
`prefers-reduced-motion` support
|
|
48
|
+
- **Dark mode**: follows `prefers-color-scheme` by default, with a manual
|
|
49
|
+
toggle in the top bar (persisted in `localStorage`)
|
|
50
|
+
- **i18n**: ships with English and Simplified Chinese; defaults to the
|
|
51
|
+
browser's `navigator.language`, switch via the `EN/中` top-bar button
|
|
52
|
+
(persisted in `localStorage`)
|
|
53
|
+
- **List virtualization** kicks in automatically above 500 files per directory
|
|
54
|
+
so huge home dirs stay smooth
|
|
55
|
+
|
|
56
|
+
## Screenshots
|
|
57
|
+
|
|
58
|
+
<table>
|
|
59
|
+
<tr>
|
|
60
|
+
<td width="50%"><img src="docs/screenshots/main-en.png" alt="Main view (English)"><br/><sub><b>English UI (dark theme)</b> — stats panel up top, sortable file list below. Switch via the EN/中 button in the top bar.</sub></td>
|
|
61
|
+
<td width="50%"><img src="docs/screenshots/main-zh.png" alt="Main view (中文)"><br/><sub><b>中文 UI</b> — same view, different language. Preference persists in localStorage.</sub></td>
|
|
62
|
+
</tr>
|
|
63
|
+
<tr>
|
|
64
|
+
<td width="50%"><img src="docs/screenshots/search.png" alt="Global fuzzy search"><br/><sub><b>Fuzzy search</b> — subsequence matching with score-ranked results and match highlighting.</sub></td>
|
|
65
|
+
<td width="50%"><img src="docs/screenshots/top-by-type.png" alt="Top-N by file type"><br/><sub><b>Top-N by type</b> — click any category in the stats panel to see its largest files; N is adjustable.</sub></td>
|
|
66
|
+
</tr>
|
|
67
|
+
<tr>
|
|
68
|
+
<td width="50%"><img src="docs/screenshots/preview-text.png" alt="In-browser text preview"><br/><sub><b>Text preview</b> — line numbers, word-wrap toggle, 1 MB cap; transparent <code>.gz</code> decompression for bio formats.</sub></td>
|
|
69
|
+
<td width="50%"><img src="docs/screenshots/login.png" alt="Login page"><br/><sub><b>Login</b> — PAM auth against real system accounts on a whitelist.</sub></td>
|
|
70
|
+
</tr>
|
|
71
|
+
</table>
|
|
72
|
+
|
|
73
|
+
## How it works
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
┌───────────────────────────────┐ ┌────────────────────────────┐
|
|
77
|
+
│ Browser │ HTTPS │ uvicorn + FastAPI (root) │
|
|
78
|
+
│ - cookie session │◀──────▶│ - PAM auth │
|
|
79
|
+
│ - no mutation of fs directly │ │ - per-request helper.py │
|
|
80
|
+
└───────────────────────────────┘ │ subprocess: │
|
|
81
|
+
│ initgroups/setgid/setuid│
|
|
82
|
+
│ to the logged-in user │
|
|
83
|
+
│ → os.scandir/open/... │
|
|
84
|
+
└──────────┬─────────────────┘
|
|
85
|
+
│ kernel-enforced
|
|
86
|
+
▼ permissions
|
|
87
|
+
/home/data/zrx/...
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- `app.py` runs as **root** (via systemd) so it can drop privileges for each
|
|
91
|
+
request.
|
|
92
|
+
- Every filesystem operation is executed by spawning `helper.py` with args
|
|
93
|
+
`--uid --gid --home …` and a sub-command (`list`, `stat`, `dirsize`, `read_stream`,
|
|
94
|
+
`write_stream`, `mkdir`, `rename`, `delete`, `search`, `stats`, `top_by_type`,
|
|
95
|
+
`trash_*`). The helper **drops privileges immediately** via
|
|
96
|
+
`os.setgroups([]) / os.initgroups / os.setgid / os.setuid`, then does the work.
|
|
97
|
+
- A small per-session `stat` cache plus HTTP ETag / `Cache-Control` on media
|
|
98
|
+
previews keeps typical browsing snappy without sacrificing isolation.
|
|
99
|
+
|
|
100
|
+
## Prerequisites
|
|
101
|
+
|
|
102
|
+
- Linux with `systemd`
|
|
103
|
+
- Python 3.11+ (uses `tomllib`; tested on 3.12)
|
|
104
|
+
- `libpam0g-dev` headers (Debian/Ubuntu) to build the `python-pam` wheel
|
|
105
|
+
- A PAM service on the host (usually already present — `login`,
|
|
106
|
+
`common-auth`, `passwd`, and `sshd` are auto-tried as fallbacks)
|
|
107
|
+
- Root on the host (the service and `setuid` require it)
|
|
108
|
+
|
|
109
|
+
## Install & run
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# 1. Install. A virtualenv is strongly recommended so pip doesn't fight
|
|
113
|
+
# Debian's PEP 668 protection.
|
|
114
|
+
python3 -m venv /opt/filemgr-venv
|
|
115
|
+
/opt/filemgr-venv/bin/pip install git+https://github.com/Lings01/filemgr.git
|
|
116
|
+
|
|
117
|
+
# 2. Generate a config and edit the [[users]] whitelist.
|
|
118
|
+
sudo mkdir -p /etc/filemgr
|
|
119
|
+
sudo /opt/filemgr-venv/bin/filemgr init-config /etc/filemgr/config.toml
|
|
120
|
+
sudo $EDITOR /etc/filemgr/config.toml
|
|
121
|
+
|
|
122
|
+
# 3. Install and start the systemd unit.
|
|
123
|
+
sudo /opt/filemgr-venv/bin/filemgr install-service --config /etc/filemgr/config.toml
|
|
124
|
+
sudo systemctl enable --now filemgr
|
|
125
|
+
filemgr status
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Or, to try it quickly without systemd (needs sudo for real setuid):
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
sudo /opt/filemgr-venv/bin/filemgr run --config /etc/filemgr/config.toml
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The `filemgr` CLI exposes:
|
|
135
|
+
|
|
136
|
+
| Command | What it does |
|
|
137
|
+
|-------------------------------|------------------------------------------------------------|
|
|
138
|
+
| `filemgr run` | Run the server in the foreground |
|
|
139
|
+
| `filemgr init-config [PATH]` | Write a sample config.toml to `PATH` (default `./config.toml`) |
|
|
140
|
+
| `filemgr install-service` | Generate and install the systemd unit (needs root) |
|
|
141
|
+
| `filemgr uninstall-service` | Remove the systemd unit |
|
|
142
|
+
| `filemgr status` | `systemctl status filemgr` |
|
|
143
|
+
| `filemgr logs [-f] [-n N]` | `journalctl -u filemgr` |
|
|
144
|
+
| `filemgr version` | Print the version |
|
|
145
|
+
|
|
146
|
+
Config file is discovered in this order: `--config` → `$FILEMGR_CONFIG` →
|
|
147
|
+
`./config.toml` → `~/.config/filemgr/config.toml` → `/etc/filemgr/config.toml`.
|
|
148
|
+
|
|
149
|
+
Open `http://127.0.0.1:8765` (or your configured `listen_host:port`) in a
|
|
150
|
+
browser. For access from another machine, either change `listen_host` to
|
|
151
|
+
`0.0.0.0` and open a firewall port, or use an SSH tunnel:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
ssh -L 8765:127.0.0.1:8765 you@server
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
For production, put nginx/Caddy in front for TLS. A sample nginx block is
|
|
158
|
+
included at the end of this README.
|
|
159
|
+
|
|
160
|
+
### Development install
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
git clone https://github.com/Lings01/filemgr.git
|
|
164
|
+
cd filemgr
|
|
165
|
+
python3 -m venv venv
|
|
166
|
+
./venv/bin/pip install -e .
|
|
167
|
+
./venv/bin/filemgr init-config ./config.toml
|
|
168
|
+
./venv/bin/filemgr run
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Configuration (`config.toml`)
|
|
172
|
+
|
|
173
|
+
```toml
|
|
174
|
+
listen_host = "127.0.0.1"
|
|
175
|
+
listen_port = 8765
|
|
176
|
+
session_ttl_seconds = 28800
|
|
177
|
+
max_upload_bytes = 10_737_418_240
|
|
178
|
+
dirsize_max_files = 100_000
|
|
179
|
+
dirsize_timeout_seconds = 30
|
|
180
|
+
preview_text_max_bytes = 1_048_576
|
|
181
|
+
pam_service = "login"
|
|
182
|
+
trash_retention_days = 3
|
|
183
|
+
|
|
184
|
+
[[users]]
|
|
185
|
+
name = "alice"
|
|
186
|
+
root = "/home/alice"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- `pam_service`: the PAM service name used for authentication. If the primary
|
|
190
|
+
value fails, `common-auth`, `passwd`, and `sshd` are tried as fallbacks.
|
|
191
|
+
- `[[users]]`: repeatable block. `name` must exist as a system account; `root`
|
|
192
|
+
is the starting directory a user sees after login (defaults to their
|
|
193
|
+
passwd-entry home).
|
|
194
|
+
|
|
195
|
+
## Keyboard shortcuts
|
|
196
|
+
|
|
197
|
+
| Key | Action |
|
|
198
|
+
|---------------------------|------------------------------------------------|
|
|
199
|
+
| `/` or `Ctrl+F` | Focus the search box |
|
|
200
|
+
| `Enter` (in search) | Run global recursive search from home |
|
|
201
|
+
| `Esc` (in search) | Clear search, return to the current directory |
|
|
202
|
+
| `R` | Refresh the current directory |
|
|
203
|
+
| `Backspace` | Go up one level |
|
|
204
|
+
| `Enter` (on a row) | Open: enter folder / preview file |
|
|
205
|
+
| `Space` (on a row) | Toggle selection |
|
|
206
|
+
| `Delete` | Move selected to recycle bin |
|
|
207
|
+
| `F2` | Rename the selected item |
|
|
208
|
+
| `Shift+F10` / ContextMenu | Open the row action menu |
|
|
209
|
+
| `Ctrl+wheel` / pinch | Zoom (image preview; also works in PDF viewer) |
|
|
210
|
+
| `+` / `-` / `0` / `1` | Image preview: zoom in / out / fit / 1:1 |
|
|
211
|
+
|
|
212
|
+
## Security model
|
|
213
|
+
|
|
214
|
+
1. **Authentication** is delegated to PAM via `python-pam`. Failed logins pay a
|
|
215
|
+
fixed 1-second penalty to resist brute force. Only accounts listed in
|
|
216
|
+
`[[users]]` can authenticate, even if PAM would accept other users.
|
|
217
|
+
2. **Authorization** is enforced by the kernel. `app.py` forks a helper
|
|
218
|
+
subprocess and the helper calls `setgroups([]) / initgroups / setgid /
|
|
219
|
+
setuid` to the logged-in uid/gid **before touching the filesystem**. Read,
|
|
220
|
+
write, and traversal permissions are then whatever Linux says they are.
|
|
221
|
+
3. **Path validation** in the helper rejects paths that escape the configured
|
|
222
|
+
home root (via symlinks or `..`). This is a UX guard; the kernel is still
|
|
223
|
+
the ground truth.
|
|
224
|
+
4. **Session tokens** are 256-bit `secrets.token_urlsafe`, stored server-side
|
|
225
|
+
in memory. Cookies are `HttpOnly` + `SameSite=Strict`.
|
|
226
|
+
5. **The service must run as root.** This is required to drop privileges per
|
|
227
|
+
request. Running as a non-root user means filesystem operations fail.
|
|
228
|
+
|
|
229
|
+
## Recycle bin
|
|
230
|
+
|
|
231
|
+
Soft deletes move the file or folder to `~/.filemgr-trash/{id}__{name}` and
|
|
232
|
+
write a sidecar JSON under `~/.filemgr-trash/.meta/{id}.json` with the
|
|
233
|
+
original path and deletion timestamp.
|
|
234
|
+
|
|
235
|
+
- The toast after a successful delete shows an "Undo" button for 7 seconds.
|
|
236
|
+
- A recycle-bin modal (top-bar button) lists every entry, with remaining time
|
|
237
|
+
before auto-purge, per-item "Restore" / "Permanently delete", and an
|
|
238
|
+
"Empty recycle bin" button.
|
|
239
|
+
- Auto-purge happens opportunistically on every `trash_list` call and on
|
|
240
|
+
every `delete` call — no cron / systemd timer needed.
|
|
241
|
+
|
|
242
|
+
## Project layout
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
filemgr/
|
|
246
|
+
├── pyproject.toml Package + entry point definition
|
|
247
|
+
├── README.md
|
|
248
|
+
├── LICENSE
|
|
249
|
+
├── src/
|
|
250
|
+
│ └── filemgr/
|
|
251
|
+
│ ├── __init__.py
|
|
252
|
+
│ ├── app.py FastAPI app: endpoints, session, PAM, helper RPC
|
|
253
|
+
│ ├── helper.py Setuid child process; all filesystem ops live here
|
|
254
|
+
│ ├── cli.py `filemgr` CLI entry point
|
|
255
|
+
│ ├── static/ Single-page frontend (vanilla HTML/CSS/JS)
|
|
256
|
+
│ │ ├── index.html
|
|
257
|
+
│ │ ├── app.js
|
|
258
|
+
│ │ └── app.css
|
|
259
|
+
│ └── templates/
|
|
260
|
+
│ ├── config.toml.example
|
|
261
|
+
│ └── filemgr.service
|
|
262
|
+
└── docs/screenshots/
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Optional: nginx reverse proxy (HTTPS)
|
|
266
|
+
|
|
267
|
+
```nginx
|
|
268
|
+
server {
|
|
269
|
+
listen 443 ssl http2;
|
|
270
|
+
server_name files.example.com;
|
|
271
|
+
|
|
272
|
+
ssl_certificate /etc/letsencrypt/live/files.example.com/fullchain.pem;
|
|
273
|
+
ssl_certificate_key /etc/letsencrypt/live/files.example.com/privkey.pem;
|
|
274
|
+
|
|
275
|
+
client_max_body_size 10G;
|
|
276
|
+
proxy_request_buffering off; # stream uploads
|
|
277
|
+
proxy_buffering off; # stream downloads
|
|
278
|
+
proxy_read_timeout 3600s;
|
|
279
|
+
proxy_send_timeout 3600s;
|
|
280
|
+
|
|
281
|
+
location / {
|
|
282
|
+
proxy_pass http://127.0.0.1:8765;
|
|
283
|
+
proxy_set_header Host $host;
|
|
284
|
+
proxy_set_header X-Forwarded-For $remote_addr;
|
|
285
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Non-goals
|
|
291
|
+
|
|
292
|
+
- Not a cloud storage service: no federation, no external auth, no sharing
|
|
293
|
+
links to strangers.
|
|
294
|
+
- Not a full OS file manager replacement: no desktop icons, no mount
|
|
295
|
+
management, no permission editor (use your shell).
|
|
296
|
+
- Not hardened against a malicious service operator: the service runs as root,
|
|
297
|
+
so whoever controls the box controls the files anyway. The guarantees here
|
|
298
|
+
are between end users of the same box, not against root.
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
MIT — see [LICENSE](LICENSE).
|