# Skydick Data Pool — User & Service Guide

Server: `skydick` (10.0.1.1), pool: `dick`

Network: bonded 2×40G ConnectX-3 (LACP 802.3ad, interface `bond40g`), jumbo frames MTU 9200, subnet 10.0.0.0/16.

Pool: 10 × Seagate Exos/Mach2 ST14000NM0001 14TB SAS (4Kn), each presenting 2 LUNs.
8 mirrors (from 8 drives) + 2 hot spares + 2 × Intel DC P4600 Optane 750GB. ~50.9T usable.
NVMe tier: mirrored 690G special vdev (metadata + small blocks ≤128K) + mirrored 8G SLOG.
See `datapool.nix` header comments for the full drive inventory and mirror layout.

## Available shares

| Share | Server path | SMB name | NFS mount | Access |
|-------|-------------|----------|-----------|--------|
| Public files | `/srv/public` | `\\SKYDICK\public` | `/public` | rw, all @storage users |
| Media library | `/srv/media/library` | `\\SKYDICK\media` | `/media/library` | ro, all @storage users |
| Torrent payload | `/srv/media/data` | `\\SKYDICK\torrents` | `/media` (via `data/`) | ro, all @storage users — browse & re-seed |
| Personal files | `/srv/users/<you>/files` | `\\SKYDICK\<you>` | `/users/<you>` | rw, SMB owner-authenticated; NFS network-trusted all_squash to owner |

The NFS mount paths in the table above are for Linux/Unix NFSv4 clients using the pseudo-root
(`/srv` on the server, exported with `fsid=0`).

Windows `Client for NFS` is different: Microsoft documents it as supporting only NFSv2/v3, not
NFSv4.1, so it does not use the pseudo-root form above. Windows clients must mount the strict
exported server path instead, for example:

- `10.0.1.1:/srv/public`
- `10.0.1.1:/srv/media/library`
- `10.0.1.1:/srv/users/<you>`

For Windows clients, SMB is the preferred protocol unless a workflow specifically requires NFS.

The final storage layout is live on `skydick`:

- `dick/public` mounted at `/srv/public`
- `dick/media` mounted at `/srv/media`, with `data/` and `library/` directories in one hardlink domain
- `dick/users/<user>/{files,bt-state,vm}` for per-user private data
- `dick/system/{backup,vm}` for centrally managed system storage
- `dick/templates/vm` for shared read-only VM base images
- `dick` should carry `atime=off`, so child datasets inherit a read-optimized default unless explicitly overridden

The old `dick/{share,backup,torrent,vm}` layout is no longer part of the design. Torrent payload now
lives under `/srv/media/data`, and organized media under `/srv/media/library`.

## Identity and authentication

- `skydick` resolves POSIX users and groups from LDAP at `ldap://10.0.0.1/`, base
  `dc=skyw,dc=top`
- SMB now uses Samba's LDAP passdb (`ldapsam`) against the same directory tree
- On this standalone server, the Samba account-domain object is expected to be
  `sambaDomainName=SKYDICK`, matching the NetBIOS name, not the browse workgroup `WORKGROUP`
- Shared `public` / `media` access is carried by the LDAP `posixGroup`
  `cn=storage,ou=posix_groups,dc=skyw,dc=top` with `gidNumber: 997`
- NFS still does not authenticate users; it trusts client IPs and export options
- LDAP remains the password source of truth. After a user's SMB access is bootstrapped once,
  password changes should happen through the LDAP password-change flow (the password web UI or
  `ldappasswd`), which keeps Samba's `sambaSamAccount` password data aligned.

Current admin users on skydick intentionally use the same canonical usernames as their LDAP
identities, for example `ye-lw21`. In those collisions, local NSS lookup still wins for the final
Unix UID/GID/group resolution on the server, while SMB password data still comes from LDAP.

The bootstrap LDIF for the Samba domain object, the LDAP `storage` group, and the machine OU is
checked in at [`samba-ldap-bootstrap.ldif`](samba-ldap-bootstrap.ldif).

## Quick start

### What a user needs before using the data pool

- A real LDAP account under `ou=people,dc=skyw,dc=top`
- For shared `public` and `media` access: membership in LDAP group
  `cn=storage,ou=posix_groups,dc=skyw,dc=top`
- For private personal storage: a provisioned `/srv/users/<you>` subtree on `skydick`
- For SMB: one-time admin bootstrap with `smbpasswd -a <you>`

After the one-time SMB bootstrap, your ongoing password is your LDAP password. Change it through
the LDAP password-change flow, not routine `smbpasswd` use.

### What an admin must do before telling a user "it is ready"

1. Create or verify the LDAP `posixAccount` for the user.
2. Add the user's `uid` to LDAP group `storage` if they should access `public` and `media`.
3. If the user needs private storage, provision the per-user Nix export/tmpfiles entries and the
   ZFS subtree under `dick/users/<user>`.
4. Deploy the updated `skydick` NixOS configuration if step 3 changed it.
5. If the user needs SMB, run `smbpasswd -a <user>` once on `skydick`.
6. If the bootstrap password was admin-set, tell the user to immediately change it through the LDAP
   password-change flow.
7. Verify `getent passwd <user>`, `zfs list -r dick/users/<user>` if applicable, and
   `pdbedit -L | grep '^<user>:'` if SMB was enabled.

## Password rules

- First SMB use: admin runs `smbpasswd -a <user>` on `skydick` once to create the user's
  `sambaSamAccount` in LDAP.
- Normal password changes after that: use the LDAP password web UI or `ldappasswd`.
- Do not use `smbpasswd` as the normal password-change workflow. It is for first-time SMB bootstrap
  and emergency repair only.

Example `ldappasswd` command for users who have LDAP CLI access:

```bash
ldappasswd -x -H ldap://10.0.0.1 \
  -D "uid=<you>,ou=people,dc=skyw,dc=top" \
  -W -S "uid=<you>,ou=people,dc=skyw,dc=top"
```

This changes the LDAP password, and the LDAP server keeps Samba password hashes aligned for users
that already have a `sambaSamAccount`.

## Connecting via SMB (Windows / macOS / Linux GUI)

### Windows

Open File Explorer, type in the address bar:

```
\\10.0.1.1\public
\\10.0.1.1\media
\\10.0.1.1\torrents
\\10.0.1.1\<your-username>
```

When prompted, enter your LDAP password after the one-time SMB bootstrap. For a user's first SMB
login, an admin must bootstrap the account once on skydick with `smbpasswd -a <user>`, which
creates the LDAP `sambaSamAccount` data for that user. After that, change passwords through the
LDAP password UI or `ldappasswd` so LDAP remains authoritative and SMB stays in sync.

### macOS

Finder → Go → Connect to Server (Cmd+K):

```
smb://10.0.1.1/public
smb://10.0.1.1/media
smb://10.0.1.1/torrents
smb://10.0.1.1/<your-username>
```

### Linux (GUI)

Nautilus/Dolphin/Thunar address bar:

```
smb://10.0.1.1/public
smb://10.0.1.1/<your-username>
```

### Linux (CLI / fstab)

```bash
# One-off mount
sudo mount -t cifs //10.0.1.1/public /mnt/public -o username=<you>,uid=$(id -u),gid=$(id -g)

# /etc/fstab (persistent) — store password in /root/.smbcredentials (chmod 600)
//10.0.1.1/public  /mnt/public  cifs  credentials=/root/.smbcredentials,uid=1000,gid=100,_netdev  0  0
```

`/root/.smbcredentials`:

```
username=<you>
password=<your-smb-password>
```

## Connecting via NFS (Linux)

NFS uses NFSv4 with a pseudo-root at `/srv`. Mount paths omit `/srv`.

### One-off mount

```bash
# Public shared files
sudo mount -t nfs4 10.0.1.1:/public /mnt/public

# Media library (read-only)
sudo mount -t nfs4 10.0.1.1:/media/library /mnt/media

# Your private tree (all writes become your UID via all_squash)
sudo mount -t nfs4 10.0.1.1:/users/<you> /mnt/skydick-home
```

### Persistent mount (/etc/fstab)

```
10.0.1.1:/public         /mnt/public        nfs4  rw,hard,_netdev                         0  0
10.0.1.1:/media/library  /mnt/media         nfs4  ro,hard,_netdev                         0  0
10.0.1.1:/users/<you>    /mnt/skydick-home  nfs4  rw,hard,_netdev                         0  0
```

### Performance tuning (10GbE)

For large transfers on 10GbE with jumbo frames, add NFS mount options:

```
rsize=1048576,wsize=1048576,nconnect=16
```

Example:

```
10.0.1.1:/users/ldx  /mnt/skydick  nfs4  rw,hard,rsize=1048576,wsize=1048576,nconnect=16,_netdev  0  0
```

## Connecting via NFS (Windows)

Windows `Client for NFS` does not use the NFSv4 pseudo-root paths above. Use the full exported
server path instead.

First check what the server exports:

```powershell
showmount -e 10.0.1.1
```

Then mount using the strict exported path:

```powershell
mount 10.0.1.1:/srv/public Z:
mount 10.0.1.1:/srv/media/library Y:
mount 10.0.1.1:/srv/users/<you> X:
```

Notes:

- Windows NFS access is network-trusted, not user-authenticated
- The per-user export still maps all writes to the server-side owner via `all_squash`
- If the client is Windows, prefer SMB unless you specifically need NFS

For SMB on `skydick`, keep Samba on its Linux defaults for socket buffers and SMB multichannel
unless a benchmark proves a specific override helps. The host-side low-risk storage tuning is
`atime=off` on the `dick` pool root so reads do not create extra metadata writes across child
datasets.

## Share details

### Public (`/srv/public`)

Collaborative shared space. All users in the `storage` group can read and write. New files
inherit group `storage` via setgid (mode 2775).

- SMB: read-write for `@storage`
- NFS: read-write with `root_squash` (root maps to nobody, normal UIDs pass through)

Shared access is governed by LDAP membership in `cn=storage,ou=posix_groups,dc=skyw,dc=top`.
`skydick` also keeps a local `storage` group at GID 997 so on-disk ownership, service accounts,
and same-name local admin overlays stay stable.

### Media library (`/srv/media/library`)

Read-only organized media (movies, TV, music). Managed by the automation stack (qBittorrent +
Sonarr/Radarr/Lidarr). Users consume but do not write here.

- SMB: read-only for `@storage`
- NFS: read-only via `/media/library` export

The qBittorrent + *arr writer stack runs on **door-pek** (not on skydick) and writes
`/srv/media` over NFS as the `qbittorrent` service account (UID 900, via the `/srv/media`
export's `all_squash,anonuid=900`); skydick only serves the dataset. The full `/srv/media`
dataset (including `/srv/media/data` with raw torrent payload) is therefore only *writable* by
that one service account. Hardlinks between `data/` and `library/` work because they are
directories on the same ZFS dataset.

### Torrents (`/srv/media/data`)

Read-only view of the raw per-tracker download tree (`BYRBT`, `M-Team - TP`, `TJUPT`,
`IPTorrents`, `qbit`, `tv`, `movies`, `music`, `incomplete`). door-pek's qBittorrent (UID 900)
is the **sole writer**, over NFS; everyone else gets read-only access so they can **re-seed the
same files without re-downloading** (seeding only needs to *read* the payload + the `.torrent`
piece hashes). Most users want the organized `media` share instead — this is the unorganized
payload.

- SMB: read-only for `@storage` as `\\SKYDICK\torrents`
- NFS: reachable read-only under the `/srv/media` export at `data/` (do **not** mount it
  writable from a second client — two writers to the same payload corrupts files / *arr
  hardlinks)

**Re-seeding an existing torrent** (without re-downloading the data):

1. Mount the payload read-only: `smb://10.0.1.1/torrents` (macOS/Linux GUI), or
   `sudo mount -t cifs //10.0.1.1/torrents /mnt/torrents -o ro,username=<you>,vers=3.1.1`.
2. Get the matching `.torrent` (re-grab it from the tracker, or copy it from door-pek's
   qBittorrent `BT_backup/`). The `.torrent` is tiny metadata — only the multi-GB payload must
   not be re-downloaded.
3. In **your own** client, add the torrent with the save path pointing at the existing bytes
   on the read-only mount (e.g. save-path `/mnt/torrents/movies` for a release under
   `/srv/media/data/movies/…`). Leave "skip hash check" **off**.
4. Force-recheck → it reads the pieces off the read-only mount, verifies hashes, jumps to
   100% / Seeding with 0 B downloaded. Your client's `.fastresume`/session live in **your own**
   local config (or your `/srv/users/<you>/bt-state`), never in the read-only data tree.

### Personal files (`/srv/users/<you>/files`)

Private per-user storage. Only you can access your tree.

- SMB: Samba `[homes]` share — connect as `\\SKYDICK\<you>`, authenticates with your Samba password
- SMB bootstrap: one-time `smbpasswd -a <you>` on skydick creates your `sambaSamAccount`
- SMB password changes after bootstrap: use the LDAP password UI or `ldappasswd`
- NFS: `/users/<you>` export with `all_squash` mapping all operations to your UID/GID

Your NFS export maps every client UID to your server-side UID. This means any process on any
host in 10.0.0.0/16 that mounts your export will write as you. NFS does not authenticate —
it trusts the network. For stronger isolation, use SMB (which requires a password).

### First-time user checklist

1. Confirm your LDAP username with the admin.
2. Confirm whether you should have only a private home, or also shared `public` and `media`
   access via LDAP group `storage`.
3. If you will use SMB, ask the admin whether your first-time SMB bootstrap has been done.
4. If the admin set a temporary SMB password for bootstrap, change it immediately through the LDAP
   password-change flow.
5. Connect with either SMB or NFS using the paths in this guide.

### Per-user subtree layout

```
/srv/users/<you>/
├── files/          ← personal files (SMB [homes] points here)
├── bt-state/       ← private torrent/arr client state
│   ├── watch/      ← .torrent watch directory
│   ├── session/    ← client session/resume data
│   └── config/     ← client configuration
└── vm/
    └── files/      ← VM disk images (file-backed, NFS-visible by default)
```

`bt-state` holds your torrent client's configuration and state databases. The actual media
payload lives on the shared `dick/media` dataset, not here. This separation means:
- No duplicate media storage across users
- Your client state is private and independent
- The shared media tree has one writer (the automation stack)

VM zvols (block devices for iSCSI) are created as ZFS children of `dick/users/<you>/vm/<name>`
and are managed by the admin. They are not visible in the filesystem tree.

## Admin runbook

The workflow below is the full procedure for onboarding a new data-pool user.

### Adding a new user

Admin procedure — run on skydick as root unless a step explicitly happens on the LDAP host:

### 1. Create or verify the user in LDAP

Preferred for storage-only users. The LDAP entry should already contain:

- `uid`
- `uidNumber`
- `gidNumber`
- `homeDirectory`
- `objectClass: posixAccount`

If the user should see `public` and `media`, also add their LDAP `uid` as a `memberUid` of
`cn=storage,ou=posix_groups,dc=skyw,dc=top`.

Check it on skydick:

```bash
getent passwd <newuser>
```

### 2. Add a local NixOS user only if needed

Only do this if the user needs SSH login, sudo, or an intentional local override. If you do create
a same-name local admin user, remember that skydick will use the local Unix UID/GID for on-server
authorization while SMB passwords still come from LDAP.

In `hosts/skydick/default.nix`:

```nix
users.users.<newuser> = {
  extraGroups = [ "storage" ];
  hashedPassword = "<hash>";  # mkpasswd -m yescrypt
};
```

In `modules/users.nix` (if the user needs SSH/sudo access across all hosts):

```nix
users.users.<newuser> = {
  isNormalUser = true;
  extraGroups = [ "wheel" ];
  openssh.authorizedKeys.keys = [ "ssh-ed25519 ..." ];
};
```

### 3. Add per-user tmpfiles and NFS export

Use numeric UID/GID in tmpfiles rules for LDAP-only users. This avoids boot-time dependence on NSS
name resolution.

First get the IDs:

```bash
uid=$(getent passwd <newuser> | cut -d: -f3)
gid=$(getent passwd <newuser> | cut -d: -f4)
```

In `hosts/skydick/datapool.nix`, add to `systemd.tmpfiles.rules`:

```nix
"d /srv/users/<newuser> 0700 <UID> <GID> -"
"d /srv/users/<newuser>/files 0750 <UID> <GID> -"
"d /srv/users/<newuser>/bt-state 0750 <UID> <GID> -"
"d /srv/users/<newuser>/vm 0750 <UID> <GID> -"
"d /srv/users/<newuser>/vm/files 0750 <UID> <GID> -"
```

Add to `services.nfs.server.exports`:

```
/srv/users/<newuser>  10.0.0.0/16(rw,sync,no_subtree_check,all_squash,anonuid=<UID>,anongid=<GID>)
```

Replace `<UID>` and `<GID>` with the LDAP-backed numeric IDs from `getent passwd`.

Example: the user previously called `ylw` in local NixOS config is now canonicalized to
`ye-lw21` everywhere, so the per-user share path is `/srv/users/ye-lw21`.

### 4. Deploy NixOS config

```bash
sudo git -C /etc/nixos pull && sudo nixos-rebuild switch --flake /etc/nixos
```

### 5. Create per-user ZFS datasets on skydick

The shared namespace datasets (`dick/public`, `dick/media`, `dick/system`, `dick/templates`, and
`dick/users`) already exist on the host. For a new user, create only that user's subtree:

```bash
# Get the user's UID/GID
uid=$(getent passwd <newuser> | cut -d: -f3)
gid=$(getent passwd <newuser> | cut -d: -f4)

# Create datasets
zfs create -o mountpoint=/srv/users/<newuser> -o quota=10T               dick/users/<newuser>
zfs create -o recordsize=128K -o mountpoint=/srv/users/<newuser>/files   dick/users/<newuser>/files
zfs create -o recordsize=16K  -o mountpoint=/srv/users/<newuser>/bt-state dick/users/<newuser>/bt-state
zfs create -o recordsize=64K  -o mountpoint=/srv/users/<newuser>/vm      dick/users/<newuser>/vm
mkdir -p /srv/users/<newuser>/vm/files

# Set ownership
chown "$uid:$gid" /srv/users/<newuser> && chmod 0700 /srv/users/<newuser>
for d in files bt-state vm vm/files; do
  chown "$uid:$gid" /srv/users/<newuser>/$d && chmod 0750 /srv/users/<newuser>/$d
done
```

`dick` is expected to have `atime=off`; the child datasets above inherit it automatically. Verify
after creation:

```bash
zfs get atime dick dick/users/<newuser> dick/users/<newuser>/files
```

### 6. Bootstrap SMB login if the user needs SMB

```bash
smbpasswd -a <newuser>
```

This one-time step is required even if the user already exists as a POSIX account in LDAP.
`smbpasswd -a` creates the user's `sambaSamAccount` attributes in LDAP and sets an initial SMB
password.

After this bootstrap, future password changes should happen through the LDAP password UI or
`ldappasswd`, not routine `smbpasswd` use. That keeps LDAP as the password source of truth while
the LDAP server updates the Samba password hashes.

The user can now connect via SMB and NFS.

### 7. Re-export NFS

```bash
exportfs -ra
```

### 8. Verify before handing over to the user

```bash
getent passwd <newuser>
id <newuser>
zfs list -r dick/users/<newuser>
exportfs -v | grep "/srv/users/<newuser>"
```

If SMB was enabled:

```bash
pdbedit -L | grep "^<newuser>:"
```

Then tell the user:

- Their exact username
- Whether they have `public` / `media` access or only the private home
- Whether SMB bootstrap is done
- To use the LDAP password-change flow for future password changes

### Existing user enabling SMB for the first time

If a user already exists in LDAP and already has the private dataset/export, but has never used SMB
before, only this step is needed:

```bash
smbpasswd -a <user>
```

After that, the user should change the password through the LDAP password-change flow if the admin
set the initial value.

### Provisioning an iSCSI block volume (admin)

iSCSI hands a client a raw block device (a ZFS zvol) instead of a shared filesystem. Unlike SMB/NFS,
**iSCSI targets are imperative state**: `services.target.enable = true` in the Nix config only installs
the restore unit `iscsi-target.service` (note: *not* `target.service`), which runs `targetctl restore`
from `/etc/target/saveconfig.json` at boot. You build the target with `targetcli` and persist it with
`saveconfig`; it is **not** declared in the repo.

An iSCSI "account" = a target LUN + a per-initiator **ACL** (allowlists the client's initiator IQN) +
**CHAP** (username/password). Both together gate access.

Prerequisites:

- The client's initiator IQN, from `cat /etc/iscsi/initiatorname.iscsi` on the client (needs root).
- A name for the volume/target. Central/host-owned LUNs live under `dick/system/vm/<name>`;
  user-owned ones under `dick/users/<user>/vm/<name>`.
- **Check pool headroom first** — a thick zvol reserves its full size immediately. On a near-full pool
  use a thin/sparse volume (`-s`), which reserves nothing and consumes only real writes:
  `zfs list dick` (watch `AVAIL`).

Steps (run on skydick as root):

```bash
# 1. Create the backing zvol. -s = thin/sparse. 16K volblocksize per pool convention.
zfs create -s -V 8T -o volblocksize=16K dick/system/vm/<name>

# 2. Build the LIO target. Generate a CHAP secret (openssl is not on the box):
CHAP=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 16); echo "$CHAP"
CLIENT_IQN=iqn.1993-08.org.debian:01:xxxxxxxxxxxx      # from the client
TGT=iqn.2026-07.top.skyw.skydick:<name>

targetcli <<EOF
/backstores/block create name=<name> dev=/dev/zvol/dick/system/vm/<name>
/backstores/block/<name> set attribute emulate_tpu=1
/iscsi create $TGT
/iscsi/$TGT/tpg1/luns create /backstores/block/<name>
/iscsi/$TGT/tpg1/acls create $CLIENT_IQN
/iscsi/$TGT/tpg1/acls/$CLIENT_IQN set auth userid=<name> password=$CHAP
/iscsi/$TGT/tpg1 set attribute authentication=1 generate_node_acls=0 cache_dynamic_acls=0
/iscsi/$TGT/tpg1/portals delete ::0 3260
/iscsi/$TGT/tpg1/portals create 10.0.1.1 3260
saveconfig
EOF
```

Notes / gotchas:

- `emulate_tpu=1` enables UNMAP so client discard/`fstrim` returns freed blocks to the thin pool.
- LIO auto-creates a default portal on the **IPv6 wildcard `[::0]:3260`**, not `0.0.0.0`. Delete
  `::0 3260` *before* creating a specific `10.0.1.1 3260` portal, or the specific bind fails with
  `EINVAL` (the wildcard already holds the port).
- `generate_node_acls=0` + `authentication=1` means only the listed IQN, with the correct CHAP, can
  attach. Firewall port 3260 is open with no source restriction, so CHAP + the IQN ACL are the guard.
- Verify: `targetcli ls /iscsi` and `ss -ltnp | grep 3260`.

Client side (open-iscsi, run as root on the client):

```bash
TGT=iqn.2026-07.top.skyw.skydick:<name>
iscsiadm -m discovery -t sendtargets -p 10.0.1.1
iscsiadm -m node -T $TGT -p 10.0.1.1 --op update -n node.session.auth.authmethod -v CHAP
iscsiadm -m node -T $TGT -p 10.0.1.1 --op update -n node.session.auth.username   -v <name>
iscsiadm -m node -T $TGT -p 10.0.1.1 --op update -n node.session.auth.password   -v <CHAP>
iscsiadm -m node -T $TGT -p 10.0.1.1 --login
iscsiadm -m node -T $TGT -p 10.0.1.1 --op update -n node.startup -v automatic   # reconnect on boot
```

A new block device (`/dev/sdX`) of the LUN size appears; format/LVM it as normal. Mount with `discard`
or run periodic `fstrim` so deletes flow back to the thin pool.

First target provisioned this way: `dick/system/vm/rh1288v3` → `RH1288V3-CPUserver` (10.0.200.11),
2026-07-14.

## Quotas

When a user's `dick/users/<user>` dataset exists, its ZFS quota (default 10TB in the examples
above) caps the total across all child datasets (files + bt-state + vm). Check usage:

```bash
zfs list -o name,used,quota -r dick/users
```

Adjust quota:

```bash
zfs set quota=20T dick/users/<user>
```

The shared `dick/media` dataset has no per-user quota — it is managed at the service level.

## Monitoring

Check pool health:

```bash
zpool status dick
```

Check dataset usage:

```bash
zfs list -o name,used,avail,refer,quota -r dick
```

Check the datapool access-time policy:

```bash
zfs get -r atime dick
```

Check NFS exports:

```bash
exportfs -v
```

Check active NFS clients:

```bash
ss -tn | grep :2049
```

## Troubleshooting

### "Permission denied" on NFS mount

- Verify your IP is in 10.0.0.0/16: `ip addr`
- Check the export exists: `showmount -e 10.0.1.1`
- Per-user exports use `all_squash` — your local UID does not matter, everything maps to
  the server-side owner UID

### "Permission denied" writing to NFS

- Public: your server-side UID must be in the `storage` group
- Media: read-only for all users (write is via the automation service account only)
- Personal: should always work — if not, check that the ZFS datasets are mounted:
  `ssh skydick zfs list -r dick/users/<you>`

### SMB authentication fails

- Samba uses LDAP-backed `sambaSamAccount` entries for SMB auth, not just the Unix `userPassword`
- `getent passwd <user>` succeeding only proves Unix account lookup works; it does not create an SMB login
- If the user has never used SMB before, admin must run `smbpasswd -a <user>` once on skydick to
  bootstrap the `sambaSamAccount`
- After bootstrap, change the password through the LDAP password UI or `ldappasswd` so Unix and
  SMB passwords stay aligned
- If `pdbedit -L` shows the user but SMB still fails, reset with `smbpasswd -a <user>` and then
  have the user change the password again through the LDAP password-change flow
- If `public` or `media` access fails but the home share works, check LDAP `storage` membership and
  verify the `memberUid` list for `cn=storage,ou=posix_groups,dc=skyw,dc=top`

### Slow NFS transfers

- Ensure jumbo frames (MTU 9200 on skydick's `bond40g`) are set on both client and server interfaces
- Add `nconnect=16` to mount options for parallel NFS connections
- Add `rsize=1048576,wsize=1048576` for 1MB read/write blocks
- Check link speed: `ethtool <interface> | grep Speed`

### Slow SMB transfers

- Do not force Samba `socket options` buffer sizes unless you have benchmark data; Linux autotuning is usually better
- Confirm the datapool inherits `atime=off`: `zfs get -r atime dick`
- Check live SMB sessions and negotiated dialect: `smbstatus -b`
- Check the active link speed on skydick: `ethtool <interface> | grep Speed`

### Files not showing up in media library

The *arr stack (Sonarr/Radarr/Lidarr) hardlinks files from `/srv/media/data/` to
`/srv/media/library/`. If a file exists in `data/` but not in `library/`, the *arr
import/rename has not run yet. Do not manually copy files into `library/` — let the
automation stack manage it to preserve hardlinks and metadata.
