diff --git a/family/how-to/truenas-access.md b/family/how-to/truenas-access.md index c72ed474..8cc33ed8 100644 --- a/family/how-to/truenas-access.md +++ b/family/how-to/truenas-access.md @@ -168,6 +168,94 @@ ssh -t truenas_admin@mallexxx.duckdns.org ssh root@192.168.2.176 | storage/git/ | /mnt/RED_2TB/storage/git/ | Git-репозитории | | ix-apps/ | /mnt/.ix-apps/ | Docker data-root (`docker.config.dataset=RED_2TB/ix-apps`) | +## Доступ к `/mnt/RED_2TB/Edu` — группа `nas_users` (2026-09-23) + +**Было:** `Edu` и `Edu/Маскелиаде` недоступны и по WebDAV, и по SSH от `truenas_admin` — +файлы `root:root`, режим `rwxrwx---`, `group@` указывал на gid 0, а `truenas_admin` в gid 0 не +входит. Внутри лежали объекты от трёх разных владельцев (`mallexxx`, `nas:nas_users`, `root`). + +**Сделано:** рекурсивно, два прохода `midclt call filesystem.setacl` (не `chgrp` — под +`truenas_admin` он даёт `Operation not permitted`): + +1. `dacl` = все ACE с `{"BASIC":"INHERIT"}`, `group@` → `ALLOW` + полный набор прав; +2. `gid=3000` (`nas_users`) рекурсивно, `uid` без изменений. + +🔴 **Ключевое:** `group@` в NFSv4 — это **POSIX-gid файла**, а не самостоятельная запись. +Одного ACE с INHERIT **недостаточно**: без смены `gid` он матчит старую группу и не даёт ничего. + +**Проверено:** +- `Edu` → `drwxrwxr-x root:nas_users`, `gid=3000`, содержимое листится от `truenas_admin`; +- `Маскелиаде`, `Книги, учебники` → `gid=3000`, все ACE `INHERITED: True`; +- `touch` в `Edu` → `WRITE_OK`. + +**Группа `nas_users` (gid 3000):** `root`, `truenas_admin`, `mallexxx`, `nas`, `transmission` — +одна запись покрывает всех. Полный состав прав `truenas_admin`: +```bash +id truenas_admin +# uid=950 gid=950 groups=950,544(builtin_administrators),921(transmission), +# 999(docker),1000(nas),3000(nas_users) +``` + +**Артефакты:** бэкап ACL до правки — `/mnt/RED_2TB/storage/acl-edu-before-20260922-234617.json`; +скрипты — `/mnt/RED_2TB/storage/edu_acl_fix.py`, `/mnt/RED_2TB/storage/edu_chgrp_fix.py` +(копии на Mac: `~/tmp/`). Подробный рецепт — [[personal/tech/truenas-nfs4-acl-and-arrmultiuser]] +§«Sharing a whole tree by GROUP». + +> ⚠️ **Кириллица в путях (`Edu/Маскелиаде`) блокируется сканером** как `confusable_text` (HIGH) +> — дважды срывала команды. Пути брать в кавычки, кириллицу не стыковать с ASCII в одном слове, +> по возможности обходиться `find -iname`/bare-string в `getacl`. + +## Доступ с macOS в Finder — WebDAV vs SMB (2026-09-23) + +| Способ | Локалка `192.168.2.x` | Из интернета | Комментарий | +|--------|----------------------|--------------|-------------| +| **SMB** :445 | ✅ нативный, быстрый | ❌ **не проброшен и не надо** | порт 445 — цель сканеров; в OpenWrt его нет | +| **WebDAV** HTTPS | ✅ | ✅ | `https://webdav.mallexxx.duckdns.org`; Finder медленный на больших файлах | +| **NFS** :2049 | ✅ | ❌ | macOS-NFS плохо с кодировками, auth по IP | + +**Ответ на вопрос «WebDAV — единственный надёжный способ из Finder?» — нет.** +В локалке SMB надёжнее; WebDAV — то, что у тебя есть для доступа **снаружи** (обёрнут в Caddy + +duckdns). SMB наружу не пробрасывают. Правильный путь «SMB снаружи» — VPN (WG-хаб или +WG-over-Reality), тогда SMB идёт внутри тоннеля. + +**SMB-шары на `Edu` НЕТ** — есть только `old-bu`, `storage`, `t610`. Создание шары +(`midclt call sharing.smb.create`, root не нужен) — **не выполнено, ждёт решения Alex**. + +📌 **WebDAV-контейнер работает под root** (`docker top webdav` → `root … webdav --config +/config.yml`), поэтому ACL его не блокировал — `owner@` был ALLOW. Образ `hacdias/webdav` — +distroless: `docker exec webdav ls|id|cat` → `executable file not found`. UID смотреть через +`docker top`, не через exec. + +## Сервисы `/mnt/RED_2TB/docker/` — инвентарь (2026-09-23) + +**VLESS/Xray-семейство:** + +| Контейнер | Образ | Роль | +|-----------|-------|------| +| `xray-admin` | `ghcr.io/mhsanaei/3x-ui` | панель 3x-ui, конфиг `/mnt/RED_2TB/docker/xray-admin` | +| `vless-proxy` | `teddysun/xray` | клиент VLESS, туннелирует через `vpn.mallexxx.duckdns.org:443` (Telegram-бот и др.) | +| `xray-reverse-portal` | `teddysun/xray:26.4.25` | **reverse-туннель Кракен→TrueNAS** (inbound `interconn` 12346, VLESS+Reality+vision) | +| `probe-load`/`probe-burst`/`probe-test`/`vless-test` | `ghcr.io/xtls/xray-core` | одноразовые пробы, **Exited** (можно чистить, но только с разрешения) | + +**`xray-reverse-portal` (`/mnt/RED_2TB/docker/reverse-portal/config.json`):** +- inbound `interconn` — `0.0.0.0:12346`, VLESS + Reality (`dest www.cloudflare.com:443`, + `shortId 1451ef281caf5905`), клиент с `reverse.tag = "reverse-out"`; +- inbound `local` — `0.0.0.0:12345`, **SOCKS без аутентификации**, `udp: true`; +- routing: `inboundTag: ["local"] → outboundTag: "reverse-out"`. + +🔴 **Это НЕ VPN в домашнюю сеть.** Проверено живьём: SOCKS `12345` слушает, но запрос к +`192.168.2.2` через него → `000`, timeout 12 s. Весь трафик inbound `local` уходит в +`reverse-out` (на Кракена) и выходит **в интернет Кракена**, а не в `192.168.2.0/24`. +Домашняя сеть через туннель **не видна**. VLESS/Xray — прокси уровня приложений: он не даёт +интерфейса, который Finder/SMB понимает как «сеть», и SMB через SOCKS не ходит в принципе. + +**Чтобы `192.168.2.x` стали локальными — нужен WireGuard**, варианта два: +1. **WG-over-Reality** — WG-туннель, завёрнутый в уже поднятый VLESS-Reality (обход блокировки + UDP через TCP/443); минус — MTU-возня, выше задержка; +2. **VPS-хаб** по скиллу `wireguard-vps-hub` — надёжнее, но отдельная инфраструктура. + +Статус: **план не выбран, работа не начата** — Alex выбирает вариант. + ## SMB-шары (проверено 2026-09-14) | id | Имя | Путь | Purpose | enabled | diff --git a/personal/tech/truenas-nfs4-acl-and-arrmultiuser.md b/personal/tech/truenas-nfs4-acl-and-arrmultiuser.md index 59b1898f..f8c426cf 100644 --- a/personal/tech/truenas-nfs4-acl-and-arrmultiuser.md +++ b/personal/tech/truenas-nfs4-acl-and-arrmultiuser.md @@ -1,7 +1,7 @@ --- title: TrueNAS NFS4 ACL repair + cross-container media PUID created: 2026-09-02 -updated: 2026-09-22 +updated: 2026-09-23 type: tech namespace: personal tags: [truenas, nfs4, acl, midclt, arr, radarr, sonarr, jellyfin, transmission, docker, git] @@ -222,6 +222,78 @@ Working fix = **full dacl replacement** (not per-ACE edit) with **inheritance fl **Vault topology (for future sessions):** one bare repo on TrueNAS is the single hub. Mac pushes/pulls remote **`nas`**; Kraken uses remote **`origin`** — both point at `ssh://truenas_admin@mallexxx.duckdns.org/mnt/RED_2TB/storage/git/obsidian-vault.git`. Mac and Kraken never talk directly. Host `truenas` does **not** resolve as an SSH alias from the Mac — use `truenas_admin@mallexxx.duckdns.org`. +## Sharing a whole tree by GROUP (`nas_users`) — the simplest fix (2026-09-23) + +**Trigger:** a directory tree inside a dataset is unreachable — e.g. `/mnt/RED_2TB/Edu` and +`/mnt/RED_2TB/Edu/Маскелиаде` gave "no access" both via WebDAV and via SSH as `truenas_admin`, +although `getacl` showed a plain ALLOW. + +**When to prefer this over per-file/uid-950 recipes above:** the requirement is "make the whole +tree accessible to a set of humans/services", not "make one container read another's files". +One `dacl` write covers everything — no per-uid chown of thousands of files. + +🔴 **THE key insight — `group@` in NFSv4 is the file's POSIX gid, not a standalone ACE.** +Setting a recursive `setacl` with `group@ ALLOW + INHERIT` **does not grant anything by itself**: +`group@` resolves to each object's own `gid`. If the gid is still `0` (root) or `921`, the ACE +grants the *wrong* group. **The `gid` must be changed too.** + +Second trap: **`chgrp -R` as `truenas_admin` FAILS** with `Operation not permitted` on every file +(uid 950 is neither the owner nor in gid 0 — same limitation as the 2026-09-21 repo fix). So the +gid change must go through midclt (`filesystem.setacl` carries `uid`/`gid` and runs the recursive +job as root). Plan for **two passes**: pass 1 = ACL with INHERIT, pass 2 = `setacl` with the new +`gid` (and `uid` passed through unchanged). + +**Choose the group by membership, not by guess:** +```bash +id truenas_admin +# uid=950(truenas_admin) gid=950(truenas_admin) +# groups=950(truenas_admin),544(builtin_administrators),921(transmission), +# 999(docker),1000(nas),3000(nas_users) +``` +`nas_users` (gid **3000**) contains `root`, `truenas_admin`, `mallexxx`, `nas`, `transmission` — +one ACE reaches all of them. Backup-target trees on this NAS already use `nas`/`nas_users`. + +**Working recipe (VM-verified 2026-09-23 on `/mnt/RED_2TB/Edu`):** + +```python +# pass 1 — ACL: set INHERIT on ALL ACEs of the tree root (read-modify-write, keep id) +cur = getacl(PATH) # id is already populated — never build dacl from scratch +for ace in cur["acl"]: + ace["type"] = "ALLOW" # group@ must be ALLOW + if ace["tag"] == "group@": + ace["perms"] = { …, "READ_DATA":True,"WRITE_DATA":True,"EXECUTE":True, + "APPEND_DATA":True,"DELETE_CHILD":True,"DELETE":True, + "WRITE_ACL":True,"WRITE_OWNER":True } # full grant + ace["flags"] = {"BASIC": "INHERIT"} # STRING, not a boolean dict +setacl({"path":PATH,"dacl":cur["acl"], + "options":{"recursive":True,"traverse":True,"stripacl":False}}) # poll job → SUCCESS + +# pass 2 — gid: without this the group@ ACE matches the OLD group and grants nothing +setacl({"path":PATH,"uid":cur["uid"],"gid":3000,"acltype":"NFS4","dacl":cur["acl"], + "options":{"recursive":True,"traverse":True,"stripacl":False}}) +``` + +**Verification (all three):** +```bash +ls -ld /mnt/RED_2TB/Edu # drwxrwxr-x root nas_users ← gid showed up +ls /mnt/RED_2TB/Edu/ # contents now list as truenas_admin +touch /mnt/RED_2TB/Edu/.wrtest && echo WRITE_OK && rm -f /mnt/RED_2TB/Edu/.wrtest +midclt call filesystem.getacl "" # every ACE must show INHERITED: True + gid 3000 +``` +Observed: `Edu` → `drwxrwxr-x root:nas_users`, `gid=3000`; children `Маскелиаде`, +`Книги, учебники` → `gid=3000` with `INHERITED: True` on all three ACEs; `WRITE_OK`. +Pre-change ACL backed up to `/mnt/RED_2TB/storage/acl-edu-before-.json`. + +> 📌 **Cyrillic directory names inside shell commands.** `.../Edu/Маскелиаде` tripped the security +> scanner as `confusable_text` (HIGH) twice, each time blocking the command. Wrap such paths in +> double quotes and never let a Cyrillic word touch an ASCII word; where possible use `getacl` with +> the path as a bare string or `find -iname` patterns instead of typing the name by hand. + +**Mac-side corollary — WebDAV was never the blocker.** The `webdav` container runs **as root** +(`docker top webdav` → `root … webdav --config /config.yml`), and `owner@` was ALLOW, so the ACL +never denied it. Container `ls`/`id` are unavailable (`hacdias/webdav` is a distroless image — +`exec: "ls": executable file not found`), so use `docker top` to learn its uid. + ## Docker network persistency (per-service compose) - `docker network connect ` is LOST on recreate → declare `` in the service's compose `networks:`. - Caddy must co-own a network with its reverse-proxy target. transmission Web UI works because caddy is ALSO in `transmission_default`; radarr↔transmission works because both in `media_net`.