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