Плагин для ansible позволяющий удобно и быстро работать с пользовательскими катологами
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-22 23:22:39 +10:00
action_plugins v1 user_content ansible module 2026-10-19 21:53:20 +10:00
library fix skipping 2026-10-22 23:22:39 +10:00
tests v1 user_content ansible module 2026-10-19 21:53:20 +10:00
LICENSE Initial commit 2026-10-19 10:54:49 +00:00
pytest.ini v1 user_content ansible module 2026-10-19 21:53:20 +10:00
README.md v1 user_content ansible module 2026-10-19 21:53:20 +10:00

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

Установка

  1. Скопировать action_plugins/user_content.py и library/user_content.py в проект.
  2. Запустить ansible-playbook из корня проекта, где лежат эти папки.
  3. Модуль должен быть виден:
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