Update PLANNING.md and CLAUDE.md to reflect current state
- Mark Phase 1, 2, 2.5, 3 as complete - Add project structure diagram to PLANNING.md - Update CLAUDE.md: correct file layout, session auth, safe config rewrite pattern, remove stale references to index.html and Basic Auth Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
5d9a9e62ec
commit
67dacd4044
2 files changed
+84
-79
No files matched your search
@@ -4,38 +4,49 @@ Project context for Claude Code. See `PLANNING.md` for the full roadmap.
|
|||||||
|
|
||||||
## What this is
|
## What this is
|
||||||
A lightweight web GUI to manage file shares on a single home server (Proxmox
|
A lightweight web GUI to manage file shares on a single home server (Proxmox
|
||||||
LXC/VM) — a minimal NAS control panel. It manages **SMB shares**, **NFS
|
LXC/VM) — a minimal NAS control panel. Manages **SMB shares**, **NFS exports**,
|
||||||
exports**, and **quotas** by editing host config files and reloading the
|
**quotas**, and **system users** by editing host config files and reloading
|
||||||
daemons. Home/LAN use only.
|
daemons. Home/LAN use only.
|
||||||
|
|
||||||
## Files
|
## File layout
|
||||||
- `app.py` — Flask backend (API + share/export/quota logic).
|
- `app.py` — Flask backend: all API routes, auth, business logic.
|
||||||
- `index.html` — Bootstrap single-page UI.
|
- `templates/index.html` — main SPA shell (Jinja2); no inline JS or CSS.
|
||||||
- `docker-compose.yml` — optional dev/test sandbox only.
|
- `templates/login.html` — login form.
|
||||||
- `PLANNING.md` — roadmap and feature list.
|
- `static/css/app.css` — custom dark theme (GitHub-palette CSS variables).
|
||||||
|
- `static/js/utils.js` — shared helpers: `esc()`, `showToast()`, `apiFetch()`.
|
||||||
|
- `static/js/dashboard.js` — dashboard data loading.
|
||||||
|
- `static/js/shares.js` — SMB/NFS CRUD (create, edit, delete, toggle).
|
||||||
|
- `static/js/quotas.js` — quota modal logic.
|
||||||
|
- `static/js/users.js` — user CRUD + password modal.
|
||||||
|
- `static/js/main.js` — page init and 30 s auto-refresh.
|
||||||
|
- `micronas.service` — systemd unit (gunicorn, reads `/etc/micronas.env`).
|
||||||
|
- `docker-compose.yml` — dev/test sandbox only, not for production.
|
||||||
|
|
||||||
## How it runs (target)
|
## How it runs
|
||||||
Directly on the LXC/VM as a `systemd` service, Flask behind `gunicorn`.
|
Directly on the LXC/VM as a `systemd` service, Flask behind `gunicorn`.
|
||||||
**Not** in Docker for production — the app manipulates host services
|
**Not** in Docker for production — the app manipulates host services
|
||||||
(`smbd`, `nfsd`, `/etc/exports`, `exportfs`, `setquota`), so it must run on the
|
(`smbd`, `nfsd`, `/etc/exports`, `exportfs`, `setquota`).
|
||||||
host. Docker stays only as a test sandbox.
|
|
||||||
|
Auth uses **Flask sessions** with a secret key derived from `MICRONAS_PASS`.
|
||||||
|
Credentials come from `/etc/micronas.env` (`MICRONAS_USER`, `MICRONAS_PASS`).
|
||||||
|
|
||||||
## Conventions & constraints
|
## Conventions & constraints
|
||||||
- **Keep it simple.** No database, no auth framework, dependency-light. Config
|
- **Keep it simple.** No database, no auth framework, dependency-light.
|
||||||
files (`/etc/samba/smb.conf`, `/etc/exports`) are the source of truth.
|
Config files (`/etc/samba/smb.conf`, `/etc/exports`) are the source of truth.
|
||||||
- **Security basics are non-negotiable even for home use:**
|
- **Security basics are non-negotiable even for home use:**
|
||||||
- Never use `subprocess.run(..., shell=True)` with user input. Use argument
|
- Never use `subprocess.run(..., shell=True)` with user input. Use argument
|
||||||
lists.
|
lists.
|
||||||
- Validate all input: names `^[A-Za-z0-9_-]+$`; paths must resolve under
|
- Validate all input: names `^[A-Za-z0-9_-]+$`; paths must resolve under
|
||||||
`/srv`.
|
`/srv`.
|
||||||
- All routes require auth (single-user HTTP Basic Auth from an env var).
|
- All routes require a valid session (`require_auth` decorator).
|
||||||
- **Config edits must be safe:** parse into blocks and rewrite whole files for
|
- **Config edits are safe:** `_parse_smb_conf()` / `_write_smb_conf()` and
|
||||||
edit/delete — do not blindly append.
|
`_parse_exports()` / `_write_exports()` parse into blocks and rewrite the
|
||||||
|
whole file — never blindly append for edit/delete/toggle operations.
|
||||||
- LAN-only. Do not add internet-facing features.
|
- LAN-only. Do not add internet-facing features.
|
||||||
|
|
||||||
## Out of scope
|
## Out of scope
|
||||||
Backups (handled by Proxmox Backup Server), multi-user roles, databases,
|
Backups (Proxmox Backup Server), multi-user roles, databases, internet exposure,
|
||||||
internet exposure, multi-host management.
|
multi-host management.
|
||||||
|
|
||||||
## Working agreement
|
## Working agreement
|
||||||
- Work in the phases defined in `PLANNING.md`; pause after each phase for the
|
- Work in the phases defined in `PLANNING.md`; pause after each phase for the
|
||||||
|
|||||||
+56
-62
@@ -30,81 +30,75 @@ on a second client). This app does not implement backup.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Current state (baseline)
|
## Project structure
|
||||||
|
|
||||||
- `app.py` — Flask API: list/create SMB & NFS shares, set quotas.
|
```
|
||||||
- `index.html` — Bootstrap UI with SMB / NFS / Quotas tabs.
|
MicroNAS/
|
||||||
- `docker-compose.yml` — test sandbox.
|
├── app.py Flask backend (all API routes + business logic)
|
||||||
|
├── templates/
|
||||||
### Known issues to fix
|
│ ├── index.html Main SPA shell (Jinja2, no inline JS/CSS)
|
||||||
- `parse_smb()` is called (`app.py:13`) but never defined → `/api/shares` 500s.
|
│ └── login.html Login form
|
||||||
- `subprocess.run(cmd, shell=True)` with f-string input (`app.py:82`) → command
|
├── static/
|
||||||
injection as root.
|
│ ├── css/app.css Custom dark theme (GitHub-palette CSS vars)
|
||||||
- `add_smb_share` writes raw user input into `smb.conf` → config injection via
|
│ └── js/
|
||||||
newlines.
|
│ ├── utils.js esc(), showToast(), apiFetch()
|
||||||
- No authentication on any route.
|
│ ├── dashboard.js loadDashboard()
|
||||||
- Config is append-only (`open(..., 'a')`) → no working delete/edit; UI Delete
|
│ ├── shares.js SMB/NFS CRUD + edit/delete/toggle
|
||||||
buttons are wired to nothing.
|
│ ├── quotas.js Quota modal
|
||||||
- No input validation on names or paths (path traversal, share of `/`).
|
│ ├── users.js User CRUD + password modal
|
||||||
|
│ └── main.js Init + auto-refresh
|
||||||
|
├── micronas.service systemd unit
|
||||||
|
├── docker-compose.yml Dev/test sandbox only
|
||||||
|
├── README.md
|
||||||
|
└── PLANNING.md
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
### Phase 1 — Correctness & safety (must-have)
|
### Phase 1 — Correctness & safety ✅
|
||||||
- [ ] Implement `parse_smb()` so `/api/shares` works.
|
- [x] Implement `parse_smb()` so `/api/shares` works.
|
||||||
- [ ] Remove all `shell=True`; use argument-list `subprocess` calls.
|
- [x] Remove all `shell=True`; use argument-list `subprocess` calls.
|
||||||
- [ ] Validate input:
|
- [x] Validate input: names `^[A-Za-z0-9_-]+$`; paths must resolve under `/srv`.
|
||||||
- share/user names: `^[A-Za-z0-9_-]+$`
|
- [x] Session-based login (custom `/login` page, Flask sessions, secret key
|
||||||
- paths must resolve under `/srv`
|
derived from `MICRONAS_PASS`).
|
||||||
- reject anything else with a clear error.
|
- [x] `systemd` unit + `gunicorn` + README install guide.
|
||||||
- [ ] Simple login: HTTP Basic Auth, single user + password from env var,
|
|
||||||
protecting all routes.
|
|
||||||
- [ ] `systemd` unit + `gunicorn`, plus README run instructions.
|
|
||||||
|
|
||||||
### Phase 2 — Dashboard & health (the main goal)
|
### Phase 2 — Dashboard & health ✅
|
||||||
A landing page that shows the state of the server at a glance:
|
- [x] **Daemon status** — smbd / nfsd running? (green/red dot).
|
||||||
- [x] **Daemon status** — is `smbd` running? is `nfsd` running? (green/red).
|
- [x] **Disk usage** — free / used / total for `/srv` with progress bar.
|
||||||
- [x] **Disk usage** — free / used / total for `/srv`.
|
- [x] **All shares in one view** — SMB + NFS, paths, access, size on disk.
|
||||||
- [x] **All shares in one view** — every SMB share and NFS export, their paths,
|
- [x] **Per-share data used** — `du -sb` per share path.
|
||||||
and who can access them.
|
- [x] **Quota usage** — per-user progress bars from `repquota`.
|
||||||
- [x] **Per-share data used** — size on disk of each share's path (`du`).
|
- [x] Auto-refresh every 30 s + manual refresh button.
|
||||||
- [x] **Quota usage** — for each user with a quota: used vs limit, with a
|
|
||||||
progress bar.
|
|
||||||
- [x] Auto-refresh every 30 seconds + manual refresh button.
|
|
||||||
|
|
||||||
### Phase 2.5 — UX improvements (next up)
|
### Phase 2.5 — UX improvements ✅
|
||||||
Improvements identified after first real test:
|
- [x] UI redesign — GitHub-dark palette, custom CSS vars, better contrast
|
||||||
|
and typography.
|
||||||
|
- [x] Inline quota management — "Quota" button per share row opens a modal
|
||||||
|
pre-filled with the share's valid users.
|
||||||
|
- [x] Toast notifications — replaced all `alert()`.
|
||||||
|
- [x] Frontend split into `templates/` + `static/css/` + `static/js/`.
|
||||||
|
- [x] User management tab — list users (uid ≥ 1000), create, delete,
|
||||||
|
change SMB password.
|
||||||
|
- [x] Login page — replaces browser Basic Auth dialog.
|
||||||
|
|
||||||
- [x] **UI redesign** — GitHub-dark palette, better contrast, card layout,
|
### Phase 3 — Full share management ✅
|
||||||
breathing room. Custom CSS variables, no extra dependencies.
|
- [x] Working **delete** for SMB & NFS (parse config into sections, rewrite
|
||||||
- [x] **Inline quota management** — removed dedicated Quotas tab. Each SMB
|
|
||||||
share and NFS export row has a "Quota" button that opens a modal.
|
|
||||||
For SMB: lists valid_users with individual GB inputs. For NFS: manual
|
|
||||||
user + GB. Submits one API call per user in parallel.
|
|
||||||
- [x] **Toast notifications** — replaced all `alert()` with Bootstrap toasts.
|
|
||||||
- [x] **Auto-refresh every 30 s** (was 60 s).
|
|
||||||
- [ ] **User management tab** — create, delete, and change the SMB password
|
|
||||||
of NAS users without touching the command line:
|
|
||||||
- List all system users (uid ≥ 1000), show whether they have a Samba
|
|
||||||
account.
|
|
||||||
- Create user: `useradd -M -s /usr/sbin/nologin` + `smbpasswd -a`.
|
|
||||||
- Delete user: `smbpasswd -x` + `userdel`.
|
|
||||||
- Change SMB password: `smbpasswd -s` via modal.
|
|
||||||
- Backend: `/api/users` GET/POST, `/api/users/<name>` DELETE,
|
|
||||||
`/api/users/<name>/password` POST.
|
|
||||||
|
|
||||||
### Phase 3 — Full management (complete the CRUD)
|
|
||||||
- [ ] Working **delete** for SMB & NFS (parse config into blocks, rewrite the
|
|
||||||
whole file — not append-only).
|
whole file — not append-only).
|
||||||
- [ ] **Edit** existing shares (path, valid users).
|
- [x] **Edit** existing shares — name, path, valid users (SMB); clients,
|
||||||
- [ ] Enable/disable a share without deleting it.
|
ro/rw (NFS). Edit modal pre-filled with current values.
|
||||||
|
- [x] **Enable/disable** a share without deleting it — SMB uses
|
||||||
|
`available = no`; NFS comments out the export line.
|
||||||
|
- [x] Status indicator per share row (green/red dot, reduced opacity when
|
||||||
|
disabled).
|
||||||
|
|
||||||
### Phase 4 — Nice-to-have
|
### Phase 4 — Nice-to-have
|
||||||
- [ ] **WebDAV** support. Preferred approach: don't build it in Flask — have the
|
- [ ] **WebDAV** support. Preferred approach: drop an Apache/nginx config
|
||||||
app drop an Apache/nginx WebDAV config snippet, same pattern as SMB/NFS,
|
snippet — don't build it in Flask.
|
||||||
so it reuses the existing architecture.
|
|
||||||
- [ ] Simple in-browser file browser for `/srv`.
|
- [ ] Simple in-browser file browser for `/srv`.
|
||||||
|
- [ ] Auto-create share directory on add (with correct owner/permissions).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user