[2026-09-23] eagle: family/how-to/truenas-access.md personal/tech/truenas-nfs4-acl-and-arrmultiuser.md
This commit is contained in:
1 parent
564e052294
commit
a3f1cb3ab8
2 files changed
+161
-1
No files matched your search
@@ -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`.
|
||||
|
||||
Reference in new issue
Block a user