v1: cleanup, dead code removal, docs update
app.py: - Move warnings import to top-level imports - Remove MICRONAS_SECRET env var (undocumented, unnecessary) - Inline secret key derivation cleanly CLAUDE.md: - Remove stale quotas mention - Add WebDAV, setup.html, favicon to file layout - Document /etc/micronas/ permissions requirements - Document <Location> vs <Directory> for WebDAV DAV On PLANNING.md: - Mark Phase 5 complete with all shipped items - Add v1 complete marker and future ideas section README.md: - Simplify dev/test section (remove complex hash snippet) - Fix double blank line Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
c992e3f1d1
commit
9f060660a1
4 files changed
+50
-43
No files matched your search
@@ -5,48 +5,46 @@ 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. Manages **SMB shares**, **NFS exports**,
|
LXC/VM) — a minimal NAS control panel. Manages **SMB shares**, **NFS exports**,
|
||||||
**quotas**, and **system users** by editing host config files and reloading
|
**WebDAV shares**, and **system users** by editing host config files and reloading
|
||||||
daemons. Home/LAN use only.
|
daemons. Home/LAN use only.
|
||||||
|
|
||||||
## File layout
|
## File layout
|
||||||
- `app.py` — Flask backend: all API routes, auth, business logic.
|
- `app.py` — Flask backend: all API routes, auth, business logic.
|
||||||
- `templates/index.html` — main SPA shell (Jinja2); no inline JS or CSS.
|
- `templates/index.html` — main SPA shell (Jinja2); no inline JS or CSS.
|
||||||
- `templates/login.html` — login form.
|
- `templates/login.html` — login form.
|
||||||
|
- `templates/setup.html` — first-time setup form (shown when no credentials exist).
|
||||||
- `static/css/app.css` — custom dark theme (GitHub-palette CSS variables).
|
- `static/css/app.css` — custom dark theme (GitHub-palette CSS variables).
|
||||||
|
- `static/img/favicon.svg` — app icon (drive bays with green LEDs).
|
||||||
- `static/js/utils.js` — shared helpers: `esc()`, `showToast()`, `apiFetch()`.
|
- `static/js/utils.js` — shared helpers: `esc()`, `showToast()`, `apiFetch()`.
|
||||||
- `static/js/dashboard.js` — dashboard: status, IPs, shares, active connections, restart buttons.
|
- `static/js/dashboard.js` — status dots, IPs, shares overview, active SMB connections, restart buttons.
|
||||||
- `static/js/shares.js` — SMB/NFS CRUD (create, edit, delete, toggle). Edit modal is async (fetches users+groups for dropdowns).
|
- `static/js/shares.js` — SMB/NFS/WebDAV CRUD. Edit modal is async (fetches users+groups for dropdowns).
|
||||||
- `static/js/users.js` — user CRUD + password modal + group management (loadGroups, createGroup, addMember, removeMember). Caches user list in `_cachedUsers` for group table.
|
- `static/js/users.js` — user CRUD + password modal + group management + admin credential change modal.
|
||||||
- `static/js/main.js` — page init and 30 s auto-refresh (calls loadDashboard, loadShares, loadUsers, loadGroups).
|
- `static/js/main.js` — page init and 30 s auto-refresh.
|
||||||
- `micronas.service` — systemd unit (gunicorn, reads `/etc/micronas.env`).
|
- `micronas.service` — systemd unit (gunicorn `-w 1`, reads `/etc/micronas/micronas.env`).
|
||||||
|
|
||||||
## How it runs
|
## 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 -w 1`.
|
||||||
**Not** in Docker for production — the app manipulates host services
|
Single worker is required — credential updates mutate `_creds` in-process.
|
||||||
(`smbd`, `nfsd`, `/etc/exports`, `exportfs`, `setquota`).
|
**Not** in Docker for production — the app manipulates host services directly.
|
||||||
|
|
||||||
Auth uses **Flask sessions** with a secret key derived from `MICRONAS_PASS`.
|
Auth uses **Flask sessions**. Credentials stored hashed (PBKDF2-HMAC-SHA256)
|
||||||
Credentials come from `/etc/micronas.env` (`MICRONAS_USER`, `MICRONAS_PASS`).
|
in `/etc/micronas/micronas.env`. First run redirects to `/setup`.
|
||||||
|
WebDAV htpasswd at `/etc/micronas/webdav.passwd` (group `www-data`, mode 640).
|
||||||
|
Config directory `/etc/micronas/` must be mode 750, owned `root:www-data`.
|
||||||
|
|
||||||
## Conventions & constraints
|
## Conventions & constraints
|
||||||
- **Keep it simple.** No database, no auth framework, dependency-light.
|
- **Keep it simple.** No database, no auth framework, dependency-light.
|
||||||
Config 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 `/srv`.
|
||||||
- Validate all input: names `^[A-Za-z0-9_-]+$`; paths must resolve under
|
|
||||||
`/srv`.
|
|
||||||
- All routes require a valid session (`require_auth` decorator).
|
- All routes require a valid session (`require_auth` decorator).
|
||||||
- **Config edits are safe:** `_parse_smb_conf()` / `_write_smb_conf()` and
|
- **Config edits are safe:** parse into blocks, rewrite whole file — never
|
||||||
`_parse_exports()` / `_write_exports()` parse into blocks and rewrite the
|
blindly append for edit/delete/toggle operations.
|
||||||
whole file — never blindly append for edit/delete/toggle operations.
|
- WebDAV config uses `<Location>` blocks for `DAV On` (not `<Directory>`) —
|
||||||
|
required for Alias-mapped paths in Apache 2.4.
|
||||||
- LAN-only. Do not add internet-facing features.
|
- LAN-only. Do not add internet-facing features.
|
||||||
|
|
||||||
## Out of scope
|
## Out of scope
|
||||||
Backups (Proxmox Backup Server), multi-user roles, databases, internet exposure,
|
Backups, multi-user roles, databases, internet exposure, multi-host management,
|
||||||
multi-host management.
|
disk quotas (ZFS-backed LXC doesn't support `setquota`).
|
||||||
|
|
||||||
## Working agreement
|
|
||||||
- Work in the phases defined in `PLANNING.md`; pause after each phase for the
|
|
||||||
user to test.
|
|
||||||
- Explain changes briefly. Don't add heavy dependencies without asking.
|
|
||||||
+21
-3
@@ -114,9 +114,27 @@ MicroNAS/
|
|||||||
- [x] WebDAV badge in dashboard shares overview.
|
- [x] WebDAV badge in dashboard shares overview.
|
||||||
- [x] @groupname fix in SMB valid users (was incorrectly rejected by VALID_NAME).
|
- [x] @groupname fix in SMB valid users (was incorrectly rejected by VALID_NAME).
|
||||||
|
|
||||||
### Phase 5 — Nice-to-have
|
### Phase 5 — Security & UX ✅
|
||||||
- [ ] Simple in-browser file browser for `/srv`.
|
- [x] **PBKDF2 password hashing** — admin credentials stored hashed (SHA-256, 260k iterations, salt$hex) in `/etc/micronas/micronas.env`. Legacy plaintext still accepted for migration.
|
||||||
- [ ] Auto-create share directory on add (with correct owner/permissions).
|
- [x] **First-login setup page** — `/setup` route shown on fresh install, no manual env editing needed.
|
||||||
|
- [x] **Admin credential change** — ⚙ Settings modal in topbar; requires current password, logs out after change.
|
||||||
|
- [x] **Config directory** — `/etc/micronas/` with correct ownership (`root:www-data`, mode 750) auto-created by code.
|
||||||
|
- [x] **WebDAV browser access** — `Options Indexes` + `<Location>` blocks for DAV On; directories browsable in browser.
|
||||||
|
- [x] **Favicon & logo** — SVG drive-bay icon, shown in browser tab and topbar.
|
||||||
|
- [x] **www-data in owner dropdown** — selectable as folder owner for WebDAV shares.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v1 complete 🎉
|
||||||
|
|
||||||
|
All planned phases shipped. The app manages SMB, NFS, and WebDAV shares with
|
||||||
|
a dark-themed web UI, secure hashed credentials, auto-synced WebDAV passwords,
|
||||||
|
group management, dashboard with live status, and a favicon.
|
||||||
|
|
||||||
|
### Possible future additions
|
||||||
|
- Simple in-browser file browser for `/srv`.
|
||||||
|
- Auto-create share directory on add (with correct owner/permissions).
|
||||||
|
- HTTPS via Let's Encrypt / self-signed cert.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -85,18 +85,11 @@ LAN or Tailscale only — never expose to the internet.
|
|||||||
## Dev / test
|
## Dev / test
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Quick local run (no gunicorn needed, must have flask installed):
|
# Plaintext password — dev only, not for production
|
||||||
MICRONAS_USER=admin MICRONAS_PASS_HASH="$(python3 -c "
|
MICRONAS_USER=admin MICRONAS_PASS=secret flask --app app.py run --debug --host=0.0.0.0
|
||||||
import hashlib, os
|
|
||||||
s=os.urandom(16).hex()
|
|
||||||
h=hashlib.pbkdf2_hmac('sha256',b'secret',s.encode(),260000).hex()
|
|
||||||
print(f'{s}\${h}')
|
|
||||||
")" flask --app app.py run --debug --host=0.0.0.0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Or use plaintext for dev only (still supported): `MICRONAS_USER=admin MICRONAS_PASS=secret flask --app app.py run --debug`
|
SMB/NFS/user management requires root and the actual daemons — test on the LXC.
|
||||||
|
|
||||||
Note: SMB/NFS/user management calls require root and the relevant daemons — run on the actual LXC for full testing.
|
|
||||||
|
|
||||||
## Auth
|
## Auth
|
||||||
|
|
||||||
@@ -104,7 +97,6 @@ All routes require a login session. On first run you are redirected to `/setup`
|
|||||||
|
|
||||||
**Changing the admin password** — click the ⚙ Settings button in the top-right corner. Enter your current password to confirm, then set a new username and/or password. You will be logged out automatically and redirected to login.
|
**Changing the admin password** — click the ⚙ Settings button in the top-right corner. Enter your current password to confirm, then set a new username and/or password. You will be logged out automatically and redirected to login.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## User management
|
## User management
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import grp
|
|||||||
import hmac
|
import hmac
|
||||||
import shutil
|
import shutil
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import warnings
|
||||||
import subprocess
|
import subprocess
|
||||||
import functools
|
import functools
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -57,13 +58,11 @@ def _save_credentials(user, password):
|
|||||||
_creds['plain'] = ''
|
_creds['plain'] = ''
|
||||||
app.secret_key = hashlib.sha256(h.encode()).hexdigest()
|
app.secret_key = hashlib.sha256(h.encode()).hexdigest()
|
||||||
|
|
||||||
# Derive initial secret key
|
# Derive initial secret key from stored credentials
|
||||||
_secret_src = _creds['hash'] or _creds['plain'] or 'insecure-dev-key'
|
_secret_src = _creds['hash'] or _creds['plain'] or 'insecure-dev-key'
|
||||||
app.secret_key = os.environ.get('MICRONAS_SECRET') or \
|
app.secret_key = hashlib.sha256(_secret_src.encode()).hexdigest()
|
||||||
hashlib.sha256(_secret_src.encode()).hexdigest()
|
|
||||||
|
|
||||||
if not _is_configured():
|
if not _is_configured():
|
||||||
import warnings
|
|
||||||
warnings.warn('No credentials configured — first-time setup required at /setup', stacklevel=1)
|
warnings.warn('No credentials configured — first-time setup required at /setup', stacklevel=1)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user