core.line
Построчная in-place правка существующего файла (lineinfile-эквивалент).
core.line изменяет отдельные строки файла, а не перезаписывает его целиком (как core.file): present добавляет строку или заменяет первую матчащую по regexp, absent удаляет все матчащие. Запись атомарна (temp + rename), по умолчанию mode/owner/group существующего файла сохраняются. Намеренно урезанный безопасный MVP: regexp — частичное совпадение (RE2, без backrefs), replace только первого совпадения. Soul-side.
Требования
- Rootтребуется
- Сторона
soul-side - Коллекция
soulstack.files - Категория
files
fs_write_rootСостояния
core.line.present — Строка line присутствует в файле. С regexp — первая матчащая строка заменяется на line; без regexp — точная строка line добавляется, если её ещё нет.
Строка добавлена или заменена.
Первое совпадение уже равно line.
Параметры
| Параметр | Тип | Обяз. / дефолт | Описание |
|---|---|---|---|
path | string | required | Целевой файл. |
line | string | optional | Точная строка, которой управляем (обязательна для present). |
regexp | string | optional | RE2-паттерн: первая матчащая строка заменяется на line. |
insertafter | string | optional | Литерал-якорь или EOF — куда вставлять (взаимоисключаемо с insertbefore). |
insertbefore | string | optional | Литерал-якорь или BOF — куда вставлять (взаимоисключаемо с insertafter). |
create | bool | optional | Создать файл, если отсутствует. |
mode | string | optional | Права при create, восьмеричная форма, напр. "0644". |
owner | string | optional | Владелец при create (имя пользователя). |
group | string | optional | Группа-владелец при create (имя группы). |
Пример — Заменить существующую строку по regexp
- name: Ensure PasswordAuthentication is off in sshd_config module: core.line.present params: path: /etc/ssh/sshd_config regexp: '^#?PasswordAuthentication' line: 'PasswordAuthentication no'Output
| Поле | Тип | Описание |
|---|---|---|
path | string | |
matched | bool | |
replaced | bool |
core.line.absent — Удаляет матчащие строки: с regexp — все матчащие, без regexp — все точные совпадения line.
Удалена хотя бы одна строка.
Удалять нечего или файла нет.
Параметры
| Параметр | Тип | Обяз. / дефолт | Описание |
|---|---|---|---|
path | string | required | Целевой файл. |
line | string | optional | Точная строка для удаления (обязателен line или regexp). |
regexp | string | optional | RE2-паттерн: удаляются все матчащие строки. |
create | bool | optional | Создать файл, если отсутствует. |
mode | string | optional | Права при create, восьмеричная форма. |
owner | string | optional | Владелец при create (имя пользователя). |
group | string | optional | Группа-владелец при create (имя группы). |
Пример — Удалить все строки, матчащие паттерн
- name: Remove legacy include directives module: core.line.absent params: path: /etc/app/app.conf regexp: '^include\s+/etc/app/legacy/'Output
| Поле | Тип | Описание |
|---|---|---|
path | string | |
removed | int |
Заметки
- regexp — главный источник drift: применяется к каждой логической строке без терминатора \n через Go regexp.MatchString, то есть частичное совпадение (не «вся строка целиком», если вы сами не поставили якоря ^…$). CRLF не нормализуется — \r остаётся частью строки и участвует в матчинге. Backrefs не поддержаны. Невалидный паттерн — ошибка до запуска (regexp.Compile на Validate).
- present с regexp при множественном совпадении заменяет только первую матчащую строку, остальные не трогает; в output добавляется warning с числом совпавших. Это сознательный безопасный выбор, а не баг.
- Якоря insertafter/insertbefore (кроме EOF/BOF) — точное совпадение строки (литерал), не regexp: позиция вставки предсказуема. Ненайденный якорь → fallback на конец файла (EOF).
- Запись атомарна (temp + rename), не in-place truncate. По умолчанию mode/owner/group существующего файла сохраняются; явные mode/owner/group для present — override. absent их не принимает и всегда сохраняет текущие.
- Финальный перевод строки исходного файла сохраняется; пустой файл не превращается в файл с пустой строкой. Подпроцессов не запускает — чтение, редактирование и запись in-process, без shell.
Безопасность и умолчания
- regexp правит чужой файл частичным совпадением — главный источник опасной правки: без ^…$ он матчит подстроку, а в absent удаляет все совпавшие строки. Слишком широкий паттерн (особенно из недоверенной интерполяции) снесёт больше, чем задумано; пустой паттерн в absent матчит каждую строку → удалит весь файл.
- ReDoS не грозит, backrefs запрещены: движок Go regexp (RE2) даёт линейное время матчинга (catastrophic backtracking невозможен), паттерн компилируется на Validate. Backrefs (подстановка групп regexp в line) не поддержаны в MVP — убирает класс ошибок «вписали в файл не то, что думали».
- Запись атомарна (temp + rename), без частично-записанного файла: прерывание прогона (краш, OOM) не оставляет конфиг наполовину переписанным. При in-place правке mode/owner/group сохраняются — модуль не понижает права существующего файла молча.
- Паттерн должен быть фиксированным якорным от автора задачи, а не из внешнего ввода. Опасно: regexp: "${ input.user_pattern }". Безопасно: regexp: '^include\s+/etc/app/legacy/'.
- Привилегии: манифест объявляет только fs_write_root (запись за пределы /var/lib/soul-stack/), не run_as_root; правка идёт с привилегиями процесса soul-агента, для путей /etc/... на практике требует root.