Настроить или починить локальное зеркало открытых задач Redmine на устройстве: "где взять api-ключ redmine", "падает на SSL / сертификате", "как узнать user_id исполнителя".
Installs into .claude/skills of the current project.
Are you the author of Redmine Snapshot?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/dewil-redmine-snapshot)
---
name: redmine-snapshot
description: Настроить или починить локальное зеркало открытых задач Redmine на устройстве: "где взять api-ключ redmine", "падает на SSL / сертификате", "как узнать user_id исполнителя".
---
# redmine-snapshot
Скилл для первой настройки или починки локального snapshot открытых задач из Redmine через REST API. Пара скриптов держит зеркало задач команды на диске - для расчета дельт, вставки сводки в дейлики и работы без живого доступа к web-UI.
Скрипты-эталоны лежат в каноне в `scripts/redmine-snapshot.py` и `scripts/redmine-deltas.py` (top-level папка `scripts/`, не внутри папки скилла). `.AI` bootstrap типа management копирует их в `scripts/` в корне проекта - оттуда же, где их ожидает запуск (`python3 scripts/redmine-snapshot.py`).
Архитектура повторяет `telegram-snapshot`: общие секреты на устройстве, проектные указатели - в репозитории проекта.
## Когда применять
- Пользователь впервые настраивает скрипты `scripts/redmine-snapshot.py` / `redmine-deltas.py` на новом устройстве.
- Подключается новый проект - нужно создать `.redmine-snapshot.json` и проверить, что общая авторизация на устройстве уже есть.
- Первый запуск падает на SSL (`CERTIFICATE_VERIFY_FAILED`) - Redmine за корпоративным CA.
- Нужно узнать `project_id` или `user_id` исполнителей для проектного конфига.
## Когда НЕ применять
- Обычный регулярный pull (запуск `redmine-snapshot.py` за свежими задачами) - это просто Bash-команда из шаблона дейлика, отдельный скилл не нужен.
- Анализ самих задач - они уже в `_redmine-snapshot.json`, читай напрямую.
## Алгоритм
### Шаг 1. Проверить общую авторизацию устройства
```bash
ls ~/.config/redmine-snapshot/
```
Должен быть `auth.json` (redmine_url, api_key, опционально use_curl) с правами 600. Если файла нет - идем к шагу 2.
### Шаг 2. Получить API-ключ Redmine
API-ключ привязан к аккаунту пользователя:
1. Залогиниться в Redmine в браузере.
2. Открыть "My account" (`/my/account`).
3. Справа - блок "API access key", нажать "Show". Если ключа нет - "Reset".
4. Скопировать ключ (40 hex-символов).
Если блока "API access key" нет - REST API выключен администратором (Administration -> Settings -> API -> "Enable REST web service"). Без него скрипт работать не будет, нужен админ инстанса.
### Шаг 3. Заполнить auth.json
```bash
mkdir -p ~/.config/redmine-snapshot
chmod 700 ~/.config/redmine-snapshot
```
Содержимое `~/.config/redmine-snapshot/auth.json` (выставить права 600):
```json
{
"redmine_url": "https://redmine.example.com",
"api_key": "<40-hex-ключ из шага 2>",
"use_curl": false
}
```
- `redmine_url` - базовый URL инстанса без хвостового слеша.
- `use_curl` - оставить `false`. Поднять в `true` только если первый pull упал на SSL (см. шаг 5).
```bash
chmod 600 ~/.config/redmine-snapshot/auth.json
```
### Шаг 4. Подключить проект (.redmine-snapshot.json)
В корне проекта создать `.redmine-snapshot.json`:
```json
{
"tasks_root": "tasks",
"project_id": 123,
"users": {
"2551": "Иванов Иван",
"2982": "Петров Петр"
}
}
```
- `tasks_root` - папка под снапшот относительно корня проекта (дефолт `tasks/`). Файлы `_redmine-snapshot.json` и `_redmine-snapshot.prev.json` лягут туда.
- `project_id` - числовой id проекта. Узнать: открыть проект в web-UI, GET `<redmine_url>/projects.json` (с заголовком `X-Redmine-API-Key`), найти проект по `identifier`, взять его `id`. Идентификатор-строку из URL тоже принимает Redmine, но числовой `id` надежнее.
- `users` - карта `{user_id: "Отображаемое имя"}`. Ключи - **строки** (JSON), значения - как удобно читать в дельтах. `user_id` исполнителя: открыть его профиль в Redmine, id виден в URL (`/users/2551`); либо GET `<redmine_url>/users.json` (нужны права видеть пользователей). Имя в значении - произвольная подпись, в API не уходит, только для вывода.
Этот файл - **не секрет** (ключа в нем нет), можно коммитить в репозиторий проекта.
### Шаг 5. Первый запуск и типичные ошибки
```bash
cd <project-root>
python3 scripts/redmine-snapshot.py
```
Зависимостей нет - чистый stdlib (`urllib`). На выходе - `<tasks_root>/_redmine-snapshot.json` и построчный отчет по исполнителям.
**`ssl.SSLCertVerificationError: CERTIFICATE_VERIFY_FAILED`.** Redmine за корпоративным CA, которого нет в хранилище Python. На macOS системный/корпоративный CA обычно лежит в keychain, откуда его берет `curl`, но не `urllib`. Лечение - в `~/.config/redmine-snapshot/auth.json` выставить `"use_curl": true`: скрипт пойдет через curl-subprocess, который этот CA подхватывает. Перезапустить.
**`401 Unauthorized` / пустой ответ.** Неверный или отозванный api_key - перевыпустить в "My account" (шаг 2). Проверить, что ключ скопирован целиком (40 символов).
**`403 Forbidden` на конкретном исполнителе.** У аккаунта-владельца ключа нет прав видеть задачи этого пользователя в проекте. Скрипт сообщает об ошибке (`!! <имя>` в stderr), проверяет остальных и завершает сбор с кодом 1 без изменения снимка и prev. Сетевая ошибка дозапроса задачи по id не прерывает сбор: задача остается в новом снимке с пометкой "статус неизвестен". HTTP 403/404 при дозапросе задачи дает отдельную пометку "выбыла из доступа": задача не переносится в снимок и повторно не дозапрашивается. Некорректный ответ API прерывает сбор с кодом 1 без изменения пары.
**Нет `total_count` в ответе выборки.** Сбор прерывается с кодом 1: пустой список задач без общего числа не считается доказательством пустой выборки.
**`0 задач` у всех.** Чаще всего неверный `project_id` или фильтр `status_id=open` не совпадает с настройкой статусов инстанса. Проверь, что в проекте действительно есть открытые задачи на этих исполнителей, и что `project_id` числовой и верный.
### Шаг 6. Дельты
После второго и последующих запусков рядом появляется `_redmine-snapshot.prev.json` (архив предыдущего сбора). Блок изменений для дейлика:
```bash
python3 scripts/redmine-deltas.py
```
Выведет markdown "Дельты с прошлого сбора": закрытые / новые задачи, "Ушла из наблюдения", "Выбыли вместе с исполнителем", "Статус неизвестен", смена статуса, смена исполнителя. Интервал - от предыдущего успешного сбора до текущего, а не календарные сутки. При нескольких сборах за день сравниваются два последних успешных сбора.
Коды сборщика: 0 - снимок записан, 1 - загрузка или запись не удалась, 2 - нет обязательного конфига. Коды дельт: 0 - сравнение выполнено, 2 - нет текущего снимка, 3 - нет `_redmine-snapshot.prev.json`. При коде 2 дельты выводят "снимка нет: <путь>"; при коде 3 - "база для сравнения отсутствует, дельты не посчитаны", без списка новых задач. "Изменений нет" означает, что оба снимка есть и различий нет.
Сборщик дозапрашивает по id задачи из прежнего снимка, пропавшие из выборки открытых задач исполнителей. Они остаются в `users -> issues` с текущими `status`, `is_closed` (признак закрытого статуса из API) и `assigned_to_id`. Если `status.is_closed` отсутствует, `is_closed` записывается как `null`, а задача идет в "Статус неизвестен (нет признака закрытия)". При сетевой ошибке дозапроса задача сохраняется с `status: "статус неизвестен"` и `status_unknown: true`, а дельты показывают ее только в группе "Статус неизвестен (дозапрос не удался)". Закрытие по явному признаку имеет приоритет; открытая задача, переназначенная за пределы `users` конфига, попадает в "Ушла из наблюдения". Если исполнитель удален из `users` конфига, его прежние задачи выводятся строками "выбыли вместе с исполнителем <имя>". Исполнитель определяется по `assigned_to_id`, а не по ключу группы в снимке.
При ошибке записи сохраняется прежняя пара снимков, поэтому дельты можно считать по последнему успешному сбору. Ссылки на issue берутся из `redmine_url` снимка; `auth.json` для дельт не нужен.
### Шаг 7. Апгрейд скриптов в существующем проекте
Скрипты эволюционируют (новые поля, фиксы). Для обновления клиента с `.AI` используй `/canon` и `canon-sync`: pinned-процедура показывает изменения и сохраняет проектные правки/исключения. Для legacy-only клиента переход в `.AI` сначала нужно запросить отдельно через migrate. Не копируй скрипты вручную из других проектов - версии могут расходиться.
## Жесткие правила
1. **auth.json - НЕ кладем в проектную папку.** Если проектная папка синхронизируется через облако (Yandex.Disk, Dropbox, iCloud), утечка api_key в облако недопустима. Только в `~/.config/redmine-snapshot/` (локально на каждом устройстве отдельно).
2. **api_key - секрет.** В `.redmine-snapshot.json`, который коммитится, ключа быть не должно - только `project_id`, `users`, `tasks_root`.
3. **use_curl - крайняя мера.** Дефолт - `urllib`. Поднимать `use_curl` только при реальной SSL-ошибке корпоративного CA, не "на всякий случай".
4. **Снапшот - технический артефакт: его место вне синкаемой папки.** `tasks_root` принимает абсолютный путь - им снимок и уводится из проекта; скрипт предупреждает на каждом прогоне, если путь ведет внутрь. Исключение из синка после создания файла не спасает - только защищает следующий (`rules/docs-maintenance.md`, раздел "Технические артефакты в синкаемой папке").
## Связанные файлы
- `scripts/redmine-snapshot.py` в корне проекта - сбор открытых задач команды в `_redmine-snapshot.json` (urllib по умолчанию, curl-фолбэк для корпоративного CA, пейджинг по 100).
- `scripts/redmine-deltas.py` в корне проекта - расчет дельт между текущим и предыдущим snapshot для блока в дейликах.
- `~/.config/redmine-snapshot/auth.json` - локальные credentials на устройство (redmine_url, api_key, use_curl). Не в проекте.
- `.redmine-snapshot.json` в корне проекта - проектные указатели `{tasks_root, project_id, users}`. Не секрет.