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:
marcandClaude Sonnet 4.6 committed 2026-06-20 19:20:24 +02:00
1 parent c992e3f1d1
commit 9f060660a1
4 files changed
+50 -43

No files matched your search

+23 -25
View File
@@ -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
View File
@@ -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.
--- ---
+3 -11
View File
@@ -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
+3 -4
View File
@@ -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)