- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| action_plugins | ||
| library | ||
| tests | ||
| LICENSE | ||
| pytest.ini | ||
| README.md | ||
user_content
Ansible-модуль для идемпотентной раскладки контента по домашним каталогам
пользователей за один вызов на хост, вместо цикла template/copy по
каждому пользователю.
Закрывает пробел между ansible.builtin.find (найти юзеров) и
ansible.builtin.copy (положить файл), которых в стандартной библиотеке
нет как единой абстракции.
Возможности
- Discovery — найти пользователей по
home_roots,users, с фильтрамиexclude_*,min_uid/max_uid. - Один файл —
content,content_b64,src: file.j2. - Директория —
src: dir/, рекурсивно, с рендером.j2внутри. - XDG —
${XDG_DESKTOP_DIR}и другие плейсхолдеры резолвятся через~/.config/user-dirs.dirsкаждого юзера. - Скелетон —
update_skel: trueобновляет/etc/skelтем же контентом за тот же вызов. - Discovery-only —
discover_only: trueвозвращает список юзеров без записи, для связки сrsync/synchronize. --check,--diff, идемпотентность по sha256 + owner/mode.state: absent— удаляет файл или дерево.
Структура проекта (пример)
project/
├── ansible.cfg # опционально
├── site.yml # плейбук
├── action_plugins/
│ └── user_content.py # резолвит src (files/, templates/)
├── library/
│ └── user_content.py # сам модуль
├── files/
│ ├── home_files/ # файлы в ~ (dest: ".")
│ │ ├── .bashrc.j2
│ │ ├── .bash_profile.j2
│ │ └── .vimrc
│ └── desktop_files/ # файлы на рабочий стол
│ ├── company.desktop
│ └── support.desktop
├── templates/ # альтернатива files/ для .j2
│ └── bashrc.j2
├── tests/
│ ├── conftest.py
│ ├── test_resolve_dest.py
│ ├── test_resolve_users.py
│ ├── test_helpers.py
│ └── test_write_file.py
├── pytest.ini
└── README.md
action_plugins/ и library/ — стандартные имена, Ansible находит их
автоматически при запуске из корня проекта
ansible.cfg
[defaults]
library = ./library
action_plugins = ./action_plugins
Установка
- Скопировать
action_plugins/user_content.pyиlibrary/user_content.pyв проект. - Запустить
ansible-playbookиз корня проекта, где лежат эти папки. - Модуль должен быть виден:
ansible-doc -t module user_content
Если команда выводит документацию — всё настроено.
Быстрый старт
.bashrc и .bash_profile всем юзерам
files/home_files/
├── .bashrc.j2
└── .bash_profile.j2
- hosts: domain_hosts
become: true
tasks:
- name: Dotfiles в домашний каталог
user_content:
home_roots: [/home/domain]
exclude_users: [nobody, sync]
exclude_patterns: ['^svc-']
min_uid: 1000
src: home_files/
dest: "."
mode: "0644"
Один вызов модуля на хост. Внутри — цикл по всем юзерам и файлам.
Ярлык на рабочий стол с учётом локали
- name: Ярлыки на рабочий стол
user_content:
home_roots: [/home/domain]
exclude_users: [nobody]
src: desktop_files/
dest: "${XDG_DESKTOP_DIR}"
create_dirs: true
mode: "0755"
become: true
- У
ivan(русская локаль):~/Рабочий стол/company.desktop - У
john(английская):~/Desktop/company.desktop
Обновить /etc/skel и всех юзеров за один вызов
- name: Dotfiles — скелетон + существующие юзеры
user_content:
update_skel: true
home_roots: [/home/domain]
exclude_users: [nobody]
src: home_files/
dest: "."
mode: "0644"
become: true
Файлы попадут и в /etc/skel/ (для будущих юзеров), и в каждый home
(для существующих).
Один файл (без директории)
- user_content:
users: [alice, bob]
src: bashrc.j2
dest: .bashrc
mode: "0644"
Inline-контент
- user_content:
home_roots: [/home/domain]
dest: ".company_motd"
content: |
Добро пожаловать в Acme!
Поддержка: support@acme.example
mode: "0644"
Удалить у всех
- user_content:
home_roots: [/home/domain]
dest: "${XDG_DESKTOP_DIR}/old_shortcut.desktop"
state: absent
Только найти пользователей (для rsync)
- name: Найти целевых пользователей
user_content:
home_roots: [/home/domain]
exclude_users: [nobody, sync]
exclude_patterns: ['^svc-']
min_uid: 1000
discover_only: true
register: target_users
- name: Разложить профиль браузера через rsync
ansible.builtin.command:
argv:
- rsync
- -a
- --delete
- --chown={{ item.user }}:{{ item.gid }}
- --exclude=Cache
- --exclude=Code Cache
- --exclude=GPUCache
- --exclude=Singleton
- /etc/skel/browser-profile/
- "{{ item.home }}/.config/chromium/"
loop: "{{ target_users.users }}"
loop_control:
label: "{{ item.user }}"
become: true
Параметры
Discovery
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
home_roots |
list | — | Каталоги для сканирования (/home/domain) |
users |
list | — | Явный список имён (альтернатива home_roots) |
exclude_users |
list | — | Имена, которых пропустить |
exclude_uids |
list | — | UID, которых пропустить |
exclude_patterns |
list | — | Regex по имени юзера |
exclude_home_patterns |
list | — | Regex по пути home |
min_uid |
int | 1000 |
Нижняя граница UID |
max_uid |
int | 60000 |
Верхняя граница UID |
discover_only |
bool | false |
Только вернуть список, ничего не писать |
Источник
| Параметр | Тип | Описание |
|---|---|---|
src |
str | Файл/шаблон/директория на контроллере. Резолвится action-плагином |
content |
str | Текстовый контент (одиночный файл) |
content_b64 |
str | Base64 (для бинарей) |
items |
list | Используется action-плагином для src: dir/ |
template_vars |
dict | Доп. переменные для Jinja |
Назначение
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
dest |
str | — | Путь относительно home. Поддерживает ${XDG_*} |
owner |
str | юзер | Владелец файла |
group |
str | осн. группа юзера | Группа |
mode |
str | 0644 |
Права (один на все файлы в вызове) |
create_dirs |
bool | false |
Создавать промежуточные каталоги |
dir_mode |
str | 0755 |
Права на создаваемые каталоги |
backup |
bool | false |
.bak перед перезаписью |
state |
str | present |
present / absent |
follow_symlinks |
bool | false |
Следовать по симлинкам |
Скелетон
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
update_skel |
bool | false |
Обновить /etc/skel тем же контентом |
skel_path |
str | /etc/skel |
Путь к скелетону |
skel_owner |
str | root |
Владелец в скелетоне |
skel_group |
str | root |
Группа в скелетоне |
XDG-плейсхолдеры
В dest можно использовать ${XDG_*_DIR}. Модуль читает
~/.config/user-dirs.dirs каждого юзера. Поддерживаются:
${XDG_DESKTOP_DIR}— рабочий стол${XDG_DOWNLOAD_DIR}— загрузки${XDG_DOCUMENTS_DIR}— документы${XDG_MUSIC_DIR}— музыка${XDG_PICTURES_DIR}— изображения${XDG_VIDEOS_DIR}— видео${XDG_PUBLICSHARE_DIR}— общие${XDG_TEMPLATES_DIR}— шаблоны
Fallback (если конфига нет): Desktop, Downloads, Documents и т.п.
Важно: become: true обязателен, иначе root не прочитает
~/.config/user-dirs.dirs (он 0600).
Ограничения XDG в /etc/skel
В /etc/skel нет user-dirs.dirs, поэтому ${XDG_DESKTOP_DIR} там →
Desktop (английский). Если кладёшь ярлыки через update_skel: true:
- Новый русский юзер получит файлы в
~/Desktop/, а его DE создаст~/Рабочий стол/при первом логине — два каталога. - Решение: для «на рабочий стол» не используй
update_skel, клади ярлыки только существующим юзерам. Или клади в~/.local/share/applications/(одинаково для всех локалей).
Ограничения
mode/owner/group— один на весь вызов. Разные права у файлов →loopпо одиночным файлам или раздельные папки.- Большие деревья (браузерные профили 100+ МБ) — не для этого
модуля. Он читает всё в память и передаёт base64. Используй
synchronize/rsync+discover_only. --delete(синхронизация) — нет. Толькоstate: absentна всё.- SELinux-контексты — наследуются от родителя. Для
.ssh/,Desktop/может отличаться. - Симлинки в
src— читаются как файлы (содержимое цели). - Пустые директории в
src— не передаются (только файлы). dest: ""— не работает. Нужно использоватьdest: "."для home.
Граница с rsync/synchronize
| Задача | Инструмент |
|---|---|
| 2–20 мелких файлов всем юзерам | user_content |
| Discovery юзеров с фильтрами | user_content + discover_only: true |
| Браузерные профили, большие деревья | synchronize / command: rsync |
--delete, --exclude, дельта-передача |
rsync |
Тесты
pip install pytest
pytest
Пример плейбука целиком
site.yml:
- hosts: domain_hosts
become: true
vars:
domain_root: /home/domain
common_excludes:
users: [nobody, sync]
patterns: ['^svc-']
tasks:
- name: Dotfiles (.bashrc, .bash_profile)
user_content:
home_roots: ["{{ domain_root }}"]
exclude_users: "{{ common_excludes.users }}"
exclude_patterns: "{{ common_excludes.patterns }}"
min_uid: 1000
update_skel: true
src: home_files/
dest: "."
mode: "0644"
- name: Ярлыки на рабочий стол
user_content:
home_roots: ["{{ domain_root }}"]
exclude_users: "{{ common_excludes.users }}"
exclude_patterns: "{{ common_excludes.patterns }}"
min_uid: 1000
src: desktop_files/
dest: "${XDG_DESKTOP_DIR}"
create_dirs: true
mode: "0755"
- name: Профиль браузера — discovery
user_content:
home_roots: ["{{ domain_root }}"]
exclude_users: "{{ common_excludes.users }}"
exclude_patterns: "{{ common_excludes.patterns }}"
min_uid: 1000
discover_only: true
register: browser_users
- name: Профиль браузера — rsync
ansible.builtin.command:
argv:
- rsync
- -a
- --delete
- --chown={{ item.user }}:{{ item.gid }}
- --exclude=Cache
- --exclude=Code Cache
- --exclude=GPUCache
- --exclude=Singleton
- --exclude=Lock
- --exclude=*.log
- /etc/skel/browser-profile/
- "{{ item.home }}/.config/chromium/"
loop: "{{ browser_users.users }}"
loop_control:
label: "{{ item.user }}"
Лицензия
MIT