Ludwig Lehnert 70fe6076a4 initial commit
2026-02-03 16:39:37 +01:00
2026-02-03 16:39:37 +01:00
2026-02-03 16:39:37 +01:00
2026-02-03 16:39:37 +01:00
2026-02-03 16:39:37 +01:00
2026-02-03 16:39:37 +01:00
2026-02-03 16:39:37 +01:00
2026-02-03 16:39:37 +01:00
2026-02-03 16:39:37 +01:00
2026-02-03 16:39:37 +01:00

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\<shareName> are separate shares pointing to /data/shares/<shareName>.
  • 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/<username>
    • /data/shares/<shareName>
  • /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

    cp .env.example .env
    
  2. Generate self-signed TLS certs for Traefik

    ./scripts/generate-self-signed.sh ./traefik/certs files.example.com
    
  3. Create host directories

    sudo mkdir -p /srv/files/data /srv/files/remote-backup
    
  4. Start the stack

    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 = <owner+rw+ro users>
    • write list = <owner+rw users>
    • 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:

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/<shareName>.
  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-<timestamp>.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

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
Description
No description provided
Readme 46 KiB
Languages
JavaScript 57.8%
EJS 21.1%
Shell 9%
CSS 8.7%
Dockerfile 3.4%