[2026-09-23] eagle: family/how-to/truenas-access.md personal/tech/truenas-nfs4-acl-and-arrmultiuser.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-23 12:55:52 +06:00
1 parent 564e052294
commit a3f1cb3ab8
2 files changed
+161 -1

No files matched your search

+88
View File
@@ -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 |
@@ -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 "<child dir>" # 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-<ts>.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 <net> <container>` is LOST on recreate → declare `<net>` 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`.