# AAD Local File Share (Samba + Web UI) Dockerized SMB/CIFS file sharing system for a single on-prem Linux host. Authentication is via on-prem AD DS (Samba `security = ADS` with winbind). Authorization and share management live exclusively in local SQLite, driven by the web UI. ## Architecture highlights - **Authentication:** Samba joins AD as a member server (`security = ADS`). SMB1 is disabled; SMB2+ only. NTLMv2 is allowed for non-domain-joined devices. - **Authorization:** SQLite is the source of truth (no AD groups). Share membership is expanded to per-user lists in a generated Samba config file. - **Share model:** - `\\server\private` is a single share that maps to `/data/private/%U` with per-user content. - `\\server\` are separate shares pointing to `/data/shares/`. - **No per-folder ACLs:** Samba config uses `force user = filesvc` and `force group = filesvc` for shared areas. `nt acl support = no` and related toggles prevent ACL edits from clients. - **Dynamic share reload:** Web UI regenerates `/etc/samba/shares.generated.conf` atomically and Samba reloads via `smbcontrol all reload-config` on change. - **Backups:** Scheduled ISO backups with a tar-based payload (`data.tar.zst`) + SQLite `.backup` file + `manifest.json` containing hashes/sizes. ## Directory layout Host directories: - `/srv/files/data` -> `/data` - `/data/private/` - `/data/shares/` - `/srv/files/remote-backup` -> `/remote` (bind-mounted remote share) SQLite location: - `/var/lib/webui/app.db` (inside `webui` container, persisted via volume) ## Quick start 1. **Clone repo and create env file** ```bash cp .env.example .env ``` 2. **Generate self-signed TLS certs for Traefik** ```bash ./scripts/generate-self-signed.sh ./traefik/certs files.example.com ``` 3. **Create host directories** ```bash sudo mkdir -p /srv/files/data /srv/files/remote-backup ``` 4. **Start the stack** ```bash docker compose up -d ``` 5. **Open the Web UI** - `https://files.example.com/` ## Samba security notes - **SMB1 is disabled** (`server min protocol = SMB2`). - **NTLMv2** is allowed for non-domain-joined clients (`ntlm auth = ntlmv2-only`). - Recommended hardening: prefer Kerberos and domain-joined devices, set `server signing = mandatory`, and enable `smb encrypt = desired` or `required` based on performance and compliance needs. - **No ACL editing**: `nt acl support = no`, `dos filemode = no`, and `unix extensions = no` reduce client-side permission manipulation. ## Share behavior - **Private share:** `\\server\private` maps to `/data/private/%U` and creates the user directory on first connect. Directory is `0700` and owned by the user (mapped via winbind). - **Shared shares:** Each share is a distinct Samba stanza in `/etc/samba/shares.generated.conf`. Users are allowed via per-user lists from SQLite: - `valid users = ` - `write list = ` - `read only = yes` (writes allowed via `write list`) **Important:** Shared area permissions are enforced by Samba config (lists), not filesystem ACLs. All files are owned by `filesvc` inside the Samba container due to `force user/group`. The `filesvc` UID/GID is `10050` by default in both `webui` and `samba` containers so ownership stays consistent on the host bind mount. ## Web UI The Web UI authenticates via Entra ID OIDC (authorization code flow) and stores sessions in an HTTP-only cookie. Users are authorized if their UPN suffix matches `ALLOWED_UPN_SUFFIX`. ### Admin page `/admin` shows access logs and aggregated statistics with Chart.js (daily activity and action breakdown). Access is restricted to the admin user via a bcrypt hash in `.env`. To generate a bcrypt hash for the admin UPN: ```bash node -e "const bcrypt=require('bcryptjs'); const upn=process.argv[1]; console.log(bcrypt.hashSync(upn, 10));" "admin@example.com" ``` Set the output in `ADMIN_UPN_BCRYPT`. ### Pages - `/` list shares, create share - `/shares/:id` manage membership - `/admin` logs and statistics ### APIs - `POST /api/shares` create share - `GET /api/shares` list shares visible to current user - `GET /api/shares/:id` share detail - `POST /api/shares/:id/members` add/remove users or local groups - `DELETE /api/shares/:id` disable share Local groups are managed in the UI on each share page and can be assigned roles per share. ### Create share flow 1. Validate share name (strict regex, length limits, reserved names). 2. Insert into SQLite with state `creating`. 3. Create `/data/shares/`. 4. `chown root:filesvc` and `chmod 2770`. 5. Regenerate `/samba-generated/shares.generated.conf` atomically. 6. Mark share `ready`. ## Backup job Backups run on the `BACKUP_CRON` schedule and produce: - `data.tar.zst` (tar archive of `/data` with xattrs/acl metadata) - `app.db` (SQLite `.backup`) - `manifest.json` (timestamp, sizes, sha256) - `backup-.iso` containing the above, then compressed to `.iso.zst` (or `.iso.gz`) **Note:** The ISO file itself does not preserve POSIX ACLs; the tar file inside does. ### Restore steps 1. Copy the compressed ISO back from `/remote`. 2. Decompress it. 3. Extract the ISO contents. 4. Decompress `data.tar.zst` and restore `/data` on the host. 5. Restore `app.db` to the web UI volume. 6. Redeploy compose. Re-join AD if the Samba secrets volume is lost. ## Connecting clients ### Windows - `\\server\private` - `\\server\shareName` Use `DOMAIN\user` or `user@domain` when prompted. NTLMv2 is supported for non-domain-joined devices. ### macOS - Finder -> Go -> Connect to Server - `smb://server/private` - `smb://server/shareName` ### Linux ```bash smbclient -U user@domain //server/private smbclient -U user@domain //server/shareName ``` ## Files of interest - `docker-compose.yml` - `samba/smb.conf.template` - `webui/src/index.js` - `backupd/backup.sh`