From 9f060660a12b0e83c7ec941a0db87940e6b7d006 Mon Sep 17 00:00:00 2001 From: marc Date: Sat, 20 Jun 2026 19:20:24 +0200 Subject: [PATCH] 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 vs 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 --- CLAUDE.md | 48 +++++++++++++++++++++++------------------------- PLANNING.md | 24 +++++++++++++++++++++--- README.md | 14 +++----------- app.py | 7 +++---- 4 files changed, 50 insertions(+), 43 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 48abe92..d9bc77c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,48 +5,46 @@ Project context for Claude Code. See `PLANNING.md` for the full roadmap. ## What this is 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**, -**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. ## File layout - `app.py` — Flask backend: all API routes, auth, business logic. - `templates/index.html` — main SPA shell (Jinja2); no inline JS or CSS. - `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/img/favicon.svg` — app icon (drive bays with green LEDs). - `static/js/utils.js` — shared helpers: `esc()`, `showToast()`, `apiFetch()`. -- `static/js/dashboard.js` — dashboard: status, IPs, shares, active 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/users.js` — user CRUD + password modal + group management (loadGroups, createGroup, addMember, removeMember). Caches user list in `_cachedUsers` for group table. -- `static/js/main.js` — page init and 30 s auto-refresh (calls loadDashboard, loadShares, loadUsers, loadGroups). -- `micronas.service` — systemd unit (gunicorn, reads `/etc/micronas.env`). +- `static/js/dashboard.js` — status dots, IPs, shares overview, active SMB connections, restart buttons. +- `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 + admin credential change modal. +- `static/js/main.js` — page init and 30 s auto-refresh. +- `micronas.service` — systemd unit (gunicorn `-w 1`, reads `/etc/micronas/micronas.env`). ## How it runs -Directly on the LXC/VM as a `systemd` service, Flask behind `gunicorn`. -**Not** in Docker for production — the app manipulates host services -(`smbd`, `nfsd`, `/etc/exports`, `exportfs`, `setquota`). +Directly on the LXC/VM as a `systemd` service, Flask behind `gunicorn -w 1`. +Single worker is required — credential updates mutate `_creds` in-process. +**Not** in Docker for production — the app manipulates host services directly. -Auth uses **Flask sessions** with a secret key derived from `MICRONAS_PASS`. -Credentials come from `/etc/micronas.env` (`MICRONAS_USER`, `MICRONAS_PASS`). +Auth uses **Flask sessions**. Credentials stored hashed (PBKDF2-HMAC-SHA256) +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 - **Keep it simple.** No database, no auth framework, dependency-light. Config files (`/etc/samba/smb.conf`, `/etc/exports`) are the source of truth. - **Security basics are non-negotiable even for home use:** - - Never use `subprocess.run(..., shell=True)` with user input. Use argument - lists. - - Validate all input: names `^[A-Za-z0-9_-]+$`; paths must resolve under - `/srv`. + - Never use `subprocess.run(..., shell=True)` with user input. Use argument lists. + - Validate all input: names `^[A-Za-z0-9_-]+$`; paths must resolve under `/srv`. - All routes require a valid session (`require_auth` decorator). -- **Config edits are safe:** `_parse_smb_conf()` / `_write_smb_conf()` and - `_parse_exports()` / `_write_exports()` parse into blocks and rewrite the - whole file — never blindly append for edit/delete/toggle operations. +- **Config edits are safe:** parse into blocks, rewrite whole file — never + blindly append for edit/delete/toggle operations. +- WebDAV config uses `` blocks for `DAV On` (not ``) — + required for Alias-mapped paths in Apache 2.4. - LAN-only. Do not add internet-facing features. ## Out of scope -Backups (Proxmox Backup Server), multi-user roles, databases, internet exposure, -multi-host management. - -## 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. +Backups, multi-user roles, databases, internet exposure, multi-host management, +disk quotas (ZFS-backed LXC doesn't support `setquota`). diff --git a/PLANNING.md b/PLANNING.md index cda8bb7..f984cc2 100644 --- a/PLANNING.md +++ b/PLANNING.md @@ -114,9 +114,27 @@ MicroNAS/ - [x] WebDAV badge in dashboard shares overview. - [x] @groupname fix in SMB valid users (was incorrectly rejected by VALID_NAME). -### Phase 5 — Nice-to-have -- [ ] Simple in-browser file browser for `/srv`. -- [ ] Auto-create share directory on add (with correct owner/permissions). +### Phase 5 — Security & UX ✅ +- [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. +- [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` + `` 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. --- diff --git a/README.md b/README.md index 6aa3c7e..48655fd 100644 --- a/README.md +++ b/README.md @@ -85,18 +85,11 @@ LAN or Tailscale only — never expose to the internet. ## Dev / test ```bash -# Quick local run (no gunicorn needed, must have flask installed): -MICRONAS_USER=admin MICRONAS_PASS_HASH="$(python3 -c " -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 +# Plaintext password — dev only, not for production +MICRONAS_USER=admin MICRONAS_PASS=secret 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` - -Note: SMB/NFS/user management calls require root and the relevant daemons — run on the actual LXC for full testing. +SMB/NFS/user management requires root and the actual daemons — test on the LXC. ## 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. - --- ## User management diff --git a/app.py b/app.py index f2235cf..0f5b431 100644 --- a/app.py +++ b/app.py @@ -5,6 +5,7 @@ import grp import hmac import shutil import hashlib +import warnings import subprocess import functools from pathlib import Path @@ -57,13 +58,11 @@ def _save_credentials(user, password): _creds['plain'] = '' 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' -app.secret_key = os.environ.get('MICRONAS_SECRET') or \ - hashlib.sha256(_secret_src.encode()).hexdigest() +app.secret_key = hashlib.sha256(_secret_src.encode()).hexdigest() if not _is_configured(): - import warnings warnings.warn('No credentials configured — first-time setup required at /setup', stacklevel=1)