Content multica creates in inbox/ is owned multica:multica, so obsync fell into "other" (---) and Sync could neither read nor upload Wong's submissions. Found after the real Sync sign-in on spaceship-earth; the earlier verify only tested the obsync -> multica direction. Shared folders now carry user:obsync:rwx as an access and a default entry alongside the shared user's, and verify tests the multica -> obsync direction (read + rewrite, as merge needs). Second run changed=0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
3.6 KiB
YAML
93 lines
3.6 KiB
YAML
---
|
|
# Permission model
|
|
#
|
|
# <parent>/ obsync 0710 + user:<shared>:--x traverse, no listing
|
|
# <vault>/ obsync 0710 + user:<shared>:--x traverse, no listing
|
|
# <vault>/<private>/ obsync 0700 nobody else
|
|
# <vault>/<shared>/ obsync 0770 + user:<u>:rwx and user:obsync:rwx,
|
|
# each also as a DEFAULT entry, so new
|
|
# children inherit both
|
|
#
|
|
# The user:obsync entry matters for content the SHARED user creates: that is
|
|
# owned <u>:<u>, so without a named entry obsync falls into "other" (---)
|
|
# and Sync can neither read nor upload it.
|
|
#
|
|
# The owning group is obsync's private group, so its bits grant nobody. On a
|
|
# directory that carries an ACL, the group bits of the mode ARE the ACL mask,
|
|
# which is why the modes here are 0710/0770 rather than 0700: a 0700 chmod
|
|
# would zero the mask and silently void every named entry.
|
|
#
|
|
# Anything else in the vault (folders Sync creates later, .obsidian/) is born
|
|
# private: the service runs with UMask=0077, and the umask is ignored only
|
|
# under a directory that has a default ACL — i.e. only in the shared folders.
|
|
|
|
- name: Layout | Collect the users granted shared folders
|
|
ansible.builtin.set_fact:
|
|
_obh_shared_users: "{{ obsidian_headless_shared_folders | map(attribute='user') | unique | list }}"
|
|
|
|
- name: Layout | Create the vault and its parent
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: "{{ obsidian_headless_user }}"
|
|
group: "{{ obsidian_headless_group }}"
|
|
mode: "{{ '0710' if _obh_shared_users | length > 0 else '0700' }}"
|
|
loop:
|
|
- "{{ obsidian_headless_vault_path | dirname }}"
|
|
- "{{ obsidian_headless_vault_path }}"
|
|
|
|
- name: Layout | Let shared users traverse (not list) the vault and its parent
|
|
ansible.posix.acl:
|
|
path: "{{ item.0 }}"
|
|
entity: "{{ item.1 }}"
|
|
etype: user
|
|
permissions: x
|
|
state: present
|
|
loop: >-
|
|
{{ [obsidian_headless_vault_path | dirname, obsidian_headless_vault_path]
|
|
| product(_obh_shared_users) | list }}
|
|
|
|
- name: Layout | Create the private folders
|
|
ansible.builtin.file:
|
|
path: "{{ obsidian_headless_vault_path }}/{{ item }}"
|
|
state: directory
|
|
owner: "{{ obsidian_headless_user }}"
|
|
group: "{{ obsidian_headless_group }}"
|
|
mode: "0700"
|
|
loop: "{{ obsidian_headless_private_folders }}"
|
|
|
|
- name: Layout | Create the shared folders
|
|
ansible.builtin.file:
|
|
path: "{{ obsidian_headless_vault_path }}/{{ item.path }}"
|
|
state: directory
|
|
owner: "{{ obsidian_headless_user }}"
|
|
group: "{{ obsidian_headless_group }}"
|
|
mode: "0770"
|
|
loop: "{{ obsidian_headless_shared_folders }}"
|
|
loop_control:
|
|
label: "{{ item.path }}"
|
|
|
|
# Each shared folder gets its user AND the service user, both as access and
|
|
# default entries.
|
|
- name: Layout | Build the shared-folder grants
|
|
ansible.builtin.set_fact:
|
|
_obh_grants: >-
|
|
{{ (obsidian_headless_shared_folders
|
|
+ (obsidian_headless_shared_folders
|
|
| map(attribute='path')
|
|
| map('community.general.dict_kv', 'path') | list
|
|
| map('combine', {'user': obsidian_headless_user}) | list))
|
|
| unique | list }}
|
|
|
|
- name: Layout | Grant shared folders (access and default entries)
|
|
ansible.posix.acl:
|
|
path: "{{ obsidian_headless_vault_path }}/{{ item.0.path }}"
|
|
entity: "{{ item.0.user }}"
|
|
etype: user
|
|
permissions: rwx
|
|
default: "{{ item.1 }}"
|
|
state: present
|
|
loop: "{{ _obh_grants | product([false, true]) | list }}"
|
|
loop_control:
|
|
label: "{{ item.0.path }} -> {{ item.0.user }}{{ ' (default)' if item.1 else '' }}"
|