Skip to content
EremitebPublic

About

рабочие скрипты

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

119 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mytasks — набор служебных скриптов

Коллекция bash/Python-скриптов для автоматизации повседневных задач.

Лицензия

Скрипты предоставляются "AS IS" («как есть»), без каких-либо гарантий. Подробнее см. файл LICENSE.


Конфигурационные файлы

Конфигурационные файлы скриптов находятся в папке conf/ в корне репозитория.

Скрипты, использующие конфигурацию, создают каталог conf/ при первом запуске, если его ещё нет. Рабочие *.conf содержат машинно-зависимые пути и параметры, поэтому не коммитятся; в репозитории хранятся безопасные *.conf.example.

Актуальные шаблоны:

  • auto_delete_old_files.conf.example;
  • btrfs_monitor.conf.example;
  • cloud_backup.conf.example;
  • cloud_backup_qnap.conf.example;
  • getip.conf.example;
  • log_template.conf.example;
  • sources.conf.example;
  • split_by_dash.conf.example;
  • system_monitor.conf.example.

Скопируйте нужный шаблон без суффикса .example и заполните локальными значениями. all_to_mp3.sh при необходимости читает conf/audio_to_mp3.conf; его параметры перечислены в разделе скрипта ниже.


Актуальный формат shell-скриптов

Shell-скрипты в корне репозитория используют общие соглашения:

  • Единая структура секций: SCRIPT ID / PATHS, HELPERS, ARGS, MAIN
  • Явные проверки параметров и зависимостей
  • Безопасные режимы запуска shell (set -eu или set -uo pipefail в зависимости от логики скрипта)
  • для скриптов с JSONL-логированием — каталог logs/ (переопределяется переменной окружения MYTASKS_LOG_DIR; тесты направляют её во временный каталог) и одна JSON-запись на строку;
  • ротация логов по параметру конкретного скрипта либо по встроенному лимиту.
  • обязательный режим --dry-run (короткая форма -n, если она не занята): каждый скрипт (.sh и .py) принимает его, ничего не меняет (файлы, каталоги, БД, сервисы, систему, сеть) и печатает план действий строками [dry-run] будет: …; чтение и проверки выполняются как обычно. У скриптов, которые только опрашивают состояние (мониторы), режим не пишет в БД/отчёты и не шлёт уведомления. Эталонная реализация — split_by_dash.sh (log_action, DRY_RUN, обёртки над mkdir/mv/rmdir). Режим описывается в разделе скрипта и покрывается bats-тестом «состояние не изменилось». Режим реализован во всех корневых скриптах; служебные логи самого запуска (logs/*.jsonl) допускаются и в dry-run.
  • Порядок запуска: сначала --dry-run. Любой скрипт, меняющий данные (файлы, каталоги, БД, сервисы, систему), перед боевым запуском всегда выполняется с --dry-run на тех же аргументах и том же конфиге; вывод плана просматривается (число и состав изменений, спорные и отклонённые операции, удаления) и только после этого запускается без --dry-run. Это обязательно для людей и для ИИ-агентов; агент сначала получает успешный dry-run и только потом предлагает пользователю решение с итогом dry-run; при ручной отладке на боевых данных (/mnt/copy/Music, /mnt/copy/Books, бэкапы, БД) — тоже. Если результат dry-run не совпадает с ожиданиями, боевой запуск не выполняется. Планируемые по расписанию запуски (cron, systemd) идут сразу в боевом режиме, поэтому новая логика сначала проверяется вручную.

Единый шаблон логов

Шаблон — conf/log_template.conf (пример для коммита: conf/log_template.conf.example). Shell-скрипты с JSONL-логированием читают его, если он доступен, и пишут унифицированные поля, подходящие для ELK/OpenSearch, Loki, Graylog и Splunk (@timestamp, schema.version, compat.targets, log.level, message, event.action, service.name), плюс служебные поля совместимости (script, event, msg, detail, rc и т.д.), чтобы не ломать существующие разборщики.


Скрипты

all_to_mp3.sh

Пакетная конвертация аудиофайлов в MP3 с помощью ffmpeg.

Возможности:

  • Поддерживает любые форматы, распознаваемые ffprobe
  • Обработка файлов в несколько потоков (xargs -P)
  • Встраивает обложку — внешнюю (cover.jpg, folder.jpg, front.jpg) или встроенную в аудио
  • Сохраняет структуру каталогов в выходной папке
  • Ведёт отчёт (audio_to_mp3_report.txt) и лог ошибок (audio_to_mp3_errors.log)

Конфигурация (опциональный файл conf/audio_to_mp3.conf):

Параметр По умолчанию Описание
FORMATS (все форматы) Список расширений для обработки
QUALITY 0 Качество MP3 (0 — наилучшее)
COVER_NAMES cover.jpg folder.jpg front.jpg Имена файлов обложек
JOBS nproc Количество параллельных потоков
OUTPUT_DIR /copy/Music Папка для результата

Использование:

./all_to_mp3.sh /путь/к/музыке

Режим dry-run: -n, --dry-run — только перечисляет файлы, которые были бы сконвертированы ([dry-run] будет: конвертация …) и неподдерживаемые; OUTPUT_DIR, отчёт и лог ошибок не создаются и не изменяются.


cloud_backup.sh

Резервное копирование /opt/esimych-cloud с удалённого сервера через WireGuard VPN.

Порядок работы:

  1. Поднимает WireGuard-соединение (wg-quick up), если оно не активно
  2. Подключается к удалённому серверу по SSH (sshpass)
  3. Выбирает компрессор на удалённом сервере: zstd, pigz или gzip
  4. Чистит корзины всех пользователей и версии файлов Nextcloud (occ trashbin:cleanup --all-users, occ versions:cleanup)
  5. (Опционально) оптимизирует MariaDB (mariadb-check --optimize, очистка general.log, опционально purge binlogs)
  6. Создаёт архив удалённой папки и сохраняет его в BACKUP_DIR
  7. Если архив за текущую дату уже существует и проходит проверку целостности, дозаписывает новый поток в тот же файл
  8. Если существующий архив пустой или повреждённый, удаляет его и создаёт заново
  9. Опускает WireGuard (если был поднят скриптом) — управляется параметром WG_KEEP_UP

Зависимости: wg-quick, sshpass

Конфигурация (conf/cloud_backup.conf):

Параметр По умолчанию Описание
WG_INTERFACE (обязательно) Имя WireGuard-интерфейса (напр. wg0)
REMOTE_HOST (обязательно) IP-адрес удалённого сервера
REMOTE_USER (обязательно) Логин SSH
REMOTE_PASSWORD (обязательно) Пароль SSH
SUDO_PASSWORD (пусто) Пароль для неинтерактивного sudo wg-quick; при пустом значении wg-quick вызывается напрямую
REMOTE_SSH_PORT 22 Порт SSH
REMOTE_PATH /opt/esimych-cloud Путь к папке на удалённой системе
BACKUP_DIR (обязательно) Локальная папка для резервных копий
WG_KEEP_UP 0 Оставить WireGuard активным после завершения
BACKUP_KEEP_COUNT 5 Сколько последних резервных копий и логов хранить
BACKUP_DEGRADATION_MIBS_THRESHOLD 6 Порог средней скорости (MiB/s) для события backup_degradation
OPTIMIZE_MARIADB_BEFORE_BACKUP 0 Выполнить оптимизацию MariaDB перед архивом
MARIADB_SERVICE_NAME mariadb Имя MariaDB-сервиса в docker compose
MARIADB_PURGE_BINLOGS 0 Очистить старые binary logs (PURGE BINARY LOGS)
MARIADB_TRUNCATE_GENERAL_LOG 1 Очистить general.log перед архивом

Логи: JSONL-файл logs/cloud_backup-YYYY-MM-DD-HH-MM-SS.jsonl. Хранится BACKUP_KEEP_COUNT последних файлов (тот же параметр, что и для архивов).

Имя архива: cloud_backup-YYYY-MM-DD.tar.gz или cloud_backup-YYYY-MM-DD.tar.zst

Поведение при существующем архиве:

  • tar.gz и tar.zst проверяются на целостность перед дозаписью
  • валидный архив за текущую дату дополняется новым сжатым потоком
  • битый или пустой архив удаляется и пересоздаётся
  • если локально недоступна утилита проверки (gzip или zstd), скрипт не дозаписывает, а создаёт архив заново

Встроенные исключения при архивировании (всегда включены):

  • tmp/*, cache/*, .cache/* в корне REMOTE_PATH
  • data/*/uploads/*
  • data/*/files_trashbin/uploads/*
  • data/*/cache/*
  • data/appdata_*/preview/*
  • data/appdata_*/thumbnails/*
  • data/appdata_*/css/*
  • data/appdata_*/js/*

Эти excludes жёстко заданы в скрипте и не настраиваются через conf/cloud_backup.conf.

Очистка корзины Nextcloud перед архивированием:

  • occ trashbin:cleanup --all-users физически удаляет саму папку data/<user>/files_trashbin (не только её содержимое), если она становится пустой
  • из-за этого встроенный фоновый джоб Nextcloud ExpireTrash (запускается через cron.php независимо от расписания бэкапа) затем падает с NotFoundException.../files_trashbin при каждом своём запуске
  • поэтому сразу после очистки скрипт получает datadirectory и список пользователей через occ и для каждого пользователя выполняет mkdir -p <datadir>/<user>/files_trashbin — это идемпотентно и безопасно

Производительность:

  • SSH-шифр: приоритет aes128-gcm, chacha20-poly1305 — используют аппаратное ускорение AES-NI
  • SSH-сжатие отключено (Compression=no) — tar уже сжимает данные, двойное сжатие только тратит CPU
  • Компрессор выбирается на удалённом сервере: zstd --fast=1 --threads=0, затем pigz -1, при их отсутствии — gzip -1
  • set -o pipefail на удалённой стороне — ошибка tar корректно прерывает передачу
  • JSONL-лог очищается от управляющих символов в detail, чтобы записи были устойчиво парсируемы
  • Пишутся диагностические события backup_metrics (размер/длительность/скорость) и backup_degradation при скорости ниже порога
  • Порог деградации можно задать через BACKUP_DEGRADATION_MIBS_THRESHOLD (MiB/s), по умолчанию 6

Использование:

sudo ./cloud_backup.sh

Cron (каждый день в 21:30):

30 21 * * * root /path/to/mytasks/cloud_backup.sh >> /dev/null 2>&1

Или через sudo crontab -e:

30 21 * * * /home/esimych/esimych-docs/mytasks/cloud_backup.sh

Режим dry-run: -n, --dry-run — печатает план без изменений: WireGuard не поднимается (если интерфейс неактивен, проверки удалённого сервера пропускаются), docker compose down/up, occ, оптимизация MariaDB и архивация не выполняются, папка бэкапов не создаётся; показываются выбранный компрессор (при доступном сервере), режим дозаписи и старые копии, которые были бы удалены. Повреждённый файл за сегодня не удаляется.


cloud_backup_qnap.sh

QNAP-версия cloud_backup.sh — то же резервное копирование /opt/esimych-cloud через WireGuard VPN, но запускается на самом QNAP NAS (Entware cron / Task Scheduler), а не на десктопе.

Отличия от cloud_backup.sh:

  • Shebang #!/opt/bin/bash — Entware bash напрямую (штатный /bin/sh на QTS не поддерживает массивы/local/${!var})
  • PATH дополняется /opt/sbin:/opt/bin (Entware), т.к. QTS не содержит wg-quick, wg, zstd
  • Нет sudo-кода — Task Scheduler/cron на QNAP и так выполняют скрипт от root
  • WireGuard поднимается в обход wg/wg-quick — на части моделей QTS (проверено на ядре 4.2.8 aarch64) консольная утилита wg(8) не работает в принципе (Protocol not supported), поэтому туннель поднимается напрямую через userspace UAPI-сокет wireguard-go (см. wg_up_userspace/wg_conf_get в коде)
  • Основной способ аутентификации — SSH-ключ (REMOTE_SSH_KEY); пароль используется только если одновременно заданы REMOTE_PASSWORD и доступен sshpass
  • Автоматически переподключает bind-mount /opt → ENTWARE_DATA_DIR, если он слетел после перезагрузки NAS (ensure_entware_mount)
  • Как и десктопная версия, чистит корзину и версии файлов Nextcloud (occ trashbin:cleanup --all-users, occ versions:cleanup) и пересоздаёт папку files_trashbin для каждого пользователя после очистки. Команды выполняются через имя сервиса Compose (NEXTCLOUD_SERVICE_NAME), поэтому не зависят от изменяемого container_name
  • При доступном zstd удалённый поток сжимается командой zstd -5 --threads=0 -c: уровень 5 выбран как компромисс между дополнительным сжатием и нагрузкой на удалённый сервер; при отсутствии zstd сохраняется автоматический переход на pigz -1 или gzip -1

Предварительная настройка на QNAP (разово, вручную) подробно описана в шапке самого скрипта: установка Entware, пакетов (opkg install bash wireguard-tools wireguard-go ncat xxd zstd coreutils-stat), создание конфига WireGuard-клиента с отдельным peer/IP, проверка userspace UAPI-сокета, копирование SSH-ключа на сервер, заполнение conf/cloud_backup_qnap.conf и (опционально, только для RAW_TRANSFER_ENABLED="1") открытие порта передачи в фаерволе удалённого сервера — см. шаг 8 в шапке скрипта.

Конфигурация (conf/cloud_backup_qnap.conf, см. .example):

Параметр По умолчанию Описание
WG_INTERFACE (обязательно) Имя WireGuard-интерфейса (напр. wg0-qnap)
WG_ENDPOINT (пусто) Необязательное переопределение публичного endpoint WireGuard в формате IPv4:port; если пусто, берётся Endpoint из /opt/etc/wireguard/<WG_INTERFACE>.conf
ENTWARE_DATA_DIR /share/Public/entware Постоянный каталог Entware, который скрипт при необходимости подключает в /opt
REMOTE_HOST (обязательно) Внутренний VPN-адрес удалённого сервера; при смене публичного IP не меняется
REMOTE_USER (обязательно) Логин SSH
REMOTE_PASSWORD (необязательно, пусто) Пароль SSH (используется только если найден sshpass)
REMOTE_SSH_KEY (рекомендуется) Путь к приватному SSH-ключу
REMOTE_SSH_PORT 22 Порт SSH
REMOTE_PATH /opt/esimych-cloud Путь к папке на удалённой системе
BACKUP_DIR (обязательно) Папка на QNAP для резервных копий (напр. /share/Public/backups)
WG_KEEP_UP 0 Оставить WireGuard активным после завершения
BACKUP_KEEP_COUNT 5 Сколько последних резервных копий и логов хранить
NEXTCLOUD_SERVICE_NAME nextcloud Имя Nextcloud-сервиса в docker compose для команд occ
SERVICES_START_RETRIES 3 Число попыток запуска и проверки сервисов после бэкапа
SERVICES_START_RETRY_DELAY_SEC 15 Пауза между повторными попытками запуска сервисов
OPTIMIZE_MARIADB_BEFORE_BACKUP 0 Выполнить оптимизацию MariaDB перед архивом
MARIADB_SERVICE_NAME mariadb Имя MariaDB-сервиса в docker compose
MARIADB_PURGE_BINLOGS 0 Очистить старые binary logs
MARIADB_TRUNCATE_GENERAL_LOG 1 Очистить general.log перед архивом
DB_OPTIMIZE_INTERVAL_DAYS 7 Интервал выполнения включённой оптимизации MariaDB; дата последнего запуска хранится в state/cloud_backup_qnap-db-optimize.state
SSH_CIPHERS chacha20-poly1305@openssh.com,aes128-gcm@openssh.com,aes256-gcm@openssh.com,aes128-ctr Приоритет SSH-шифров; chacha20-poly1305 первым, т.к. на слабых ARM-чипах без аппаратного AES он обычно быстрее программного AES-GCM/CTR, а расшифровку входящего потока бэкапа выполняет именно QNAP
RAW_TRANSFER_ENABLED 0 Передавать поток бэкапа через сырой TCP (nc/ncat) внутри WireGuard-туннеля в обход дополнительного слоя SSH-шифрования — см. раздел «Передача в обход SSH-шифрования» ниже. 0 — обычная передача через SSH (без изменений)
RAW_TRANSFER_PORT 8873 TCP-порт для приёма потока на удалённом сервере (нужно один раз открыть в фаерволе, см. шаг 8 в шапке скрипта)
RAW_TRANSFER_CONNECT_RETRIES 10 Число попыток подключения к листенеру (по 2 сек между попытками)
RAW_TRANSFER_REMOTE_TIMEOUT_SEC 21600 Потолок времени жизни удалённого листенера (защита от зависшего процесса, если QNAP так и не подключился)
RAW_TRANSFER_STATUS_POLL_RETRIES 5 Число попыток получить статус завершения удалённого пайплайна после приёма данных
NETWORK_SPEED_TEST_MB 50 Размер тестовой передачи (МБ) для замера реальной скорости сети QNAP↔REMOTE_HOST перед началом бэкапа (событие network_speed_test)
BACKUP_DEGRADATION_MIBS_THRESHOLD 4 Порог средней скорости, ниже которого создаётся backup_degradation

Логи: JSONL-файл logs/cloud_backup_qnap-YYYY-MM-DD-HH-MM-SS.jsonl на QNAP. Хранится BACKUP_KEEP_COUNT последних файлов (см. cleanup_logs, тот же параметр, что и для архивов).

Диагностика скорости: перед передачей и во время неё скрипт фиксирует сетевой тест, RTT, load average, доступную память и IO QNAP. Итоговое событие backup_metrics содержит размер, длительность, среднюю скорость и агрегированные ресурсные показатели. При скорости ниже BACKUP_DEGRADATION_MIBS_THRESHOLD создаётся backup_degradation с вероятной причиной. Ошибки raw-transfer дополняются снимком состояния удалённого сервера.

После бэкапа docker compose up -d и состояние контейнеров проверяются до SERVICES_START_RETRIES раз. Сбоем считаются контейнеры в состоянии unhealthy, dead, restarting или Exited с ненулевым кодом; Exited (0) (контейнеры-сироты удалённых из compose сервисов, одноразовые init-контейнеры) сбоем не считается. Если после всех попыток сбойные контейнеры остаются, в лог записывается docker compose ps -a, а сам скрипт завершается с ненулевым кодом. Оптимизация Redis и снятие его логов удалены вместе с сервисом valkey из стека (2026-10-01).

Передача в обход SSH-шифрования (RAW_TRANSFER_ENABLED): при значении 1 сжатый поток передаётся через сырой TCP внутри WireGuard-туннеля, без дополнительного SSH-шифрования. WireGuard продолжает обеспечивать конфиденциальность и целостность; SSH используется для управляющих команд и запуска листенера.

Для корректного завершения потока используются:

  • удалённая сторона: nc -N -l ... — без флага -N OpenBSD nc не закрывает сокет по EOF своего stdin и висит до RAW_TRANSFER_REMOTE_TIMEOUT_SEC (часы) вместо того, чтобы завершиться сразу после отправки всех данных;
  • сторона QNAP: ncat --recv-only ... < /dev/null — флаг запрещает отправку локального stdin, а перенаправление позволяет процессу завершиться после получения EOF от сервера.

Порт RAW_TRANSFER_PORT нужно открыть в фаерволе удалённого сервера. При недоступности nc или исчерпании попыток подключения скрипт автоматически переходит на SSH-передачу.

Имя архива и поведение при дозаписи — идентично cloud_backup.sh (см. выше): cloud_backup_qnap-YYYY-MM-DD.tar.zst/.tar.gz, проверка целостности и дозапись валидного архива за текущую дату, встроенные excludes для кэшей/preview/tmp.

Планирование запуска на QNAP — Планировщик задач QTS (если доступен) либо персистентный crontab (в отличие от обычного Linux, /etc/config/crontab переживает перезагрузку NAS, т.к. хранится в конфигурации прошивки):

vi /etc/config/crontab
# добавить строку (например, каждый день в 04:00):
0 4 * * * /share/Public/tasks/cloud_backup_qnap.sh >/dev/null 2>&1
crontab /etc/config/crontab
/etc/init.d/crond.sh restart

Важно: не запускайте cloud_backup.sh (десктоп) и cloud_backup_qnap.sh одновременно против одного и того же REMOTE_HOST — оба скрипта независимо управляют docker compose down/up -d на удалённом сервере, и параллельный запуск может привести к преждевременному перезапуску сервисов или конфликту жизненного цикла контейнеров.

Важно: в crontab вызывайте скрипт напрямую, не через sh: явный sh /share/Public/tasks/cloud_backup_qnap.sh игнорирует шебанг #!/opt/bin/bash и запускает Bash-скрипт системным /bin/sh QTS.

Режим dry-run: -n, --dry-run — печатает план без изменений: /opt Entware не монтируется, WireGuard не поднимается (если интерфейс неактивен, проверки удалённого сервера пропускаются), замер скорости сети и диагностика нагрузки не запускаются, сервисы не останавливаются, архив не создаётся, файл состояния оптимизации БД не обновляется; показываются компрессор, режим дозаписи и старые копии, которые были бы удалены.


auto_delete_old_files.sh

Автоматически удаляет старые файлы по сроку хранения из указанной папки.

Конфигурация (conf/auto_delete_old_files.conf):

Параметр По умолчанию Описание
TARGET_DIR (обязательно) Каталог, где нужно удалять старые файлы
FILE_TYPES all Типы файлов: all или список расширений (log,tmp,bak)
KEEP_DAYS 30 Сколько дней хранить файлы

Правило удаления: удаляются только файлы (-type f) старше KEEP_DAYS дней.

Использование:

./auto_delete_old_files.sh

Cron (каждый день в 20:30):

30 20 * * * /home/esimych/esimych-docs/mytasks/auto_delete_old_files.sh >/dev/null 2>&1

Режим dry-run: -n, --dry-run — перечисляет файлы, которые были бы удалены ([dry-run] будет: удалён файл: …), ничего не удаляя; в конце печатает число подошедших файлов.


check-store-mount.sh

Проверяет, смонтированы ли все точки монтирования из /etc/fstab.

Возможности:

  • Пропускает виртуальные ФС (/proc, /sys, /dev, swap и др.)
  • При обнаружении несмонтированных точек отправляет desktop-уведомление через notify-send
  • Дублирует сообщение в консоль
  • Предназначен для запуска через cron или autostart

Использование:

./check-store-mount.sh

Режим dry-run: -n, --dry-run — проверка выполняется как обычно, но уведомление notify-send не отправляется, а только печатается.


check_backups.sh

Проверяет каталоги с архивами резервных копий и пишет результат в JSONL-лог; при любой ошибке дополнительно показывает уведомление в трее (notify-send).

Что проверяется для каждого каталога из BACKUP_DIRS:

  • каталог доступен и читаем (обращение запускает автомонтирование NFS; зависание ограничено ACCESS_TIMEOUT_SECS);
  • в нём есть архивы по маскам ARCHIVE_PATTERNS (по умолчанию *.tar.zst *.tar.gz *.tgz *.tar *.zip *.7z);
  • самый свежий архив не старше MAX_AGE_DAYS (для записи каталог|N — свой лимит) и не меньше MIN_SIZE_BYTES;
  • размер свежего архива не упал ниже MIN_SIZE_RATIO_PCT % от предыдущего (признак оборванной записи);
  • целостность свежего архива при VERIFY_INTEGRITY="yes" (по умолчанию включено; zstd -t, gzip -t, unzip -t, 7z t, tar -t). Архив читается целиком, на NFS ~112 МБ/с архив ~33 ГБ проверяется ≈5 минут (замер 2026-10-04): проверка идёт с nice -n 19/ionice -c3, тайм-аут рассчитывается по размеру (размер / VERIFY_MIN_SPEED_MBPS + VERIFY_TIMEOUT_BASE_SECS, для 33 ГБ ≈28 мин; превышение — только WARN в логе), уже проверенный и неизменившийся архив повторно не читается (state/check_backups.verified), а изменённый менее VERIFY_MIN_AGE_MINUTES назад (ещё пишется) откладывается до следующего запуска.

Конфигурация: conf/check_backups.conf (пример: conf/check_backups.conf.example).

Коды возврата: 0 — всё в порядке, 1 — найдены проблемы с бэкапами, 2 — ошибка конфигурации, зависимостей или аргументов (уведомление показывается и в этом случае).

Использование:

./check_backups.sh              # проверка + лог + уведомление при ошибках
./check_backups.sh --no-notify  # без уведомления в трее

Расписание — systemd user timer (уведомлению нужна сессия пользователя, поэтому юниты пользовательские). Таймер срабатывает через 4 минуты после загрузки системы (если сеанс начался позже — сразу после входа) и далее раз в сутки при непрерывной работе (OnBootSec=4min, OnUnitActiveSec=1d). Юниты: systemd/user/check-backups.{service,timer}.

mkdir -p ~/.config/systemd/user
cp systemd/user/check-backups.service systemd/user/check-backups.timer ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now check-backups.timer

# Проверка
systemctl --user list-timers check-backups.timer
journalctl --user -u check-backups.service -n 20

Режим dry-run: -n, --dry-run — проверки выполняются как обычно, но кэш проверенных архивов (state/check_backups.verified) не изменяется, state/ не создаётся, уведомление не показывается (печатается).


btrfs_monitor.sh

Мониторинг счётчиков ошибок Btrfs на корневом разделе (или другой точке монтирования).

Что делает:

  • Читает btrfs device stats для ROOT_PATH
  • Сравнивает с прошлым запуском (state-файл)
  • При росте счётчиков выводит alert, пишет ERROR в JSONL-лог и завершает работу с кодом 1
  • На первом запуске только инициализирует baseline

Конфигурация:

  • conf/btrfs_monitor.conf
  • пример: conf/btrfs_monitor.conf.example

Использование:

./btrfs_monitor.sh

Cron (каждые 6 часов):

0 */6 * * * /home/esimych/esimych-docs/mytasks/btrfs_monitor.sh >/dev/null 2>&1

Режим dry-run: -n, --dry-run — счётчики читаются и сравниваются как обычно, но файл состояния не создаётся и не обновляется, уведомление не отправляется (печатается).


system_monitor.sh

Диагностика системы: жёсткие диски через SMART (smartctl), CPU (температура, загрузка, load average) и GPU (NVIDIA, через nvidia-smi). Опрос, запись состояния в SQLite и формирование HTML-отчёта (таблицы, диаграммы, аналитическая справка по каждому диску). Предназначен для запуска по расписанию и/или при загрузке системы.

Что делает:

Диски:

  • Определяет физические диски автоматически через lsblk (или берёт список из DISKS в конфиге), пропуская zram, loop, ram, sr, dm-
  • Опрашивает каждый диск через smartctl -a -j (JSON) и разбирает результат через jq: health, модель, серийный номер, температура, наработка (power-on hours), Reallocated_Sector_Ct, Current_Pending_Sector, Offline_Uncorrectable (для NVMe — соответствующие поля nvme_smart_health_information_log)
  • Определяет устойчивое системное имя диска (/dev/disk/by-id/..., например ata-ST4000DM004-2U9104_WW608PYW) — не меняется при перестановке дисков/перезагрузке, в отличие от /dev/sdX; хранится в SQLite вместе с остальными метриками
  • Определяет точки монтирования диска через lsblk -J + jq, с привязкой к разделу (например nvme0n1p1: /, /var/log, /var/cache, /var/tmp, /root, /srv; nvme0n1p2: /boot/efi) — на btrfs один и тот же раздел часто смонтирован сразу в несколько мест через подтома (subvolumes); JSON-разбор через jq берётся не случайно: построчный lsblk -o MOUNTPOINT (ед. число) отдаёт только одну точку на раздел и может молча потерять корень /
  • Определяет объём диска в байтах через lsblk -bdn -o SIZE (не зависит от smartctl — доступен даже если SMART недоступен/диск не поддерживается) и показывает его в отчёте в человекочитаемом виде (КиБ/МиБ/ГиБ/ТиБ)

CPU:

  • Температура — через lm_sensors (sensors -j), ищет главный тепловой датчик пакета (Tctl/Tdie на AMD, Package id 0 на Intel); опционально — если sensors не установлен/не настроен, CPU всё равно опрашивается, просто без температуры
  • Загрузка (%) — две выборки /proc/stat с интервалом в 1 секунду (тот же принцип, что использует top/mpstat), без дополнительных зависимостей
  • Load average (1/5/15 мин) — из /proc/loadavg

GPU (NVIDIA):

  • Через nvidia-smi --query-gpu=... --format=csv: модель, температура, загрузка (%), использование видеопамяти, потребляемая мощность — по каждой видеокарте отдельно (поддержка нескольких GPU)
  • Опционально — если nvidia-smi не найден (нет NVIDIA GPU или драйвера), опрос GPU просто пропускается, это не ошибка

Отчёт и хранение:

  • Пишет каждый опрос как строки в таблицы disk_stats/cpu_stats/gpu_stats в SQLite (DB_FILE), не перезаписывая историю — на её основе строятся графики. Все данные, которые попадают в отчёт, сначала сохраняются в SQLite (ничего не вычисляется «на лету» только для HTML) — это сделано специально, чтобы дальше можно было строить аналитические отчёты поверх накопленной истории
  • Формирует HTML-отчёт целиком из данных SQLite: сводные плитки (кол-во объектов в норме/внимание/критично по дискам+CPU+GPU вместе), таблица метрик по каждому диску (с колонками «Точка монтирования» и «Объём»), аналитическая справка по каждому диску (деградация поверхности, риск отказа, перегрев, точки монтирования и т.д.), таблицы и графики по CPU и GPU, график истории температуры дисков и диаграмма Reallocated/Pending-секторов — всё встроенным SVG, без внешних JS-библиотек. Графики CPU/GPU совмещают температуру (°C) и проценты (загрузка, VRAM) на одной общей шкале 0-100 — это не «две оси», а честно общий диапазон, единица измерения проставлена у каждой точки
  • При критическом состоянии любого объекта (диск: SMART FAILED/неисправимые ошибки чтения/температура выше TEMP_CRIT_C; CPU/GPU: температура выше CPU_TEMP_CRIT_C/GPU_TEMP_CRIT_C) отправляет desktop-уведомление через notify-send (как btrfs_monitor.sh / check-store-mount.sh) и завершается с кодом 1; предупреждения (переназначенные/ожидающие секторы диска, температура выше порога «внимание») только пишутся в лог, без уведомления
  • Ротирует и JSONL-логи (KEEP_LOGS), и HTML-отчёты (REPORT_KEEP)

Конфигурация:

  • conf/system_monitor.conf
  • пример: conf/system_monitor.conf.example
Параметр По умолчанию Описание
DISKS (пусто = автоопределение) Список дисков через запятую (sda,sdb), пусто — автоопределение через lsblk
DB_FILE state/system_monitor.db Путь к базе SQLite с историей опросов
REPORT_DIR system_reports/ рядом со скриптом Каталог для HTML-отчётов
REPORT_KEEP 5 Сколько последних HTML-отчётов хранить
KEEP_LOGS 10 Сколько последних JSONL-логов хранить
HISTORY_POINTS 30 Сколько последних опросов показывать на графиках истории
SMARTCTL_BIN smartctl Путь к бинарю smartctl, если не в PATH
SENSORS_BIN sensors Путь к бинарю sensors (lm_sensors), если не в PATH; опционально
NVIDIA_SMI_BIN nvidia-smi Путь к бинарю nvidia-smi, если не в PATH; опционально
TEMP_WARN_C, TEMP_CRIT_C 50, 60 Пороги температуры дисков (°C) для «внимание»/«критично»
CPU_TEMP_WARN_C, CPU_TEMP_CRIT_C 80, 90 Пороги температуры CPU (°C)
GPU_TEMP_WARN_C, GPU_TEMP_CRIT_C 75, 85 Пороги температуры GPU (°C)
APP_NAME, ICON_NAME, URGENCY SystemMonitor, utilities-system-monitor, critical Параметры desktop-уведомления

Использование:

# Опрос дисков/CPU/GPU + запись в SQLite + HTML-отчёт
sudo ./system_monitor.sh

# Только пересобрать HTML-отчёт из уже накопленных данных, без опроса
sudo ./system_monitor.sh -r

Важно: для чтения SMART-атрибутов smartctl обычно требует root — крон-запись нужно добавлять в root-crontab (sudo crontab -e), а не в обычный пользовательский. Опрос CPU/GPU root не требует, но так как весь скрипт обычно запускается под root ради дисков, они опрашиваются в том же (привилегированном) запуске.

Расписание — systemd timer (рекомендуется):

Юнит-файлы лежат в systemd/system-monitor.service и systemd/system-monitor.timer. Таймер срабатывает в 00/06/12/18 часов, а благодаря Persistent=true пропущенный слот (машина была выключена) выполняется при первой же загрузке — и не более одного раза за интервал. Обычный cron 0 */6 пропуски не догоняет: если компьютер включают, скажем, в 18:30 и выключают до полуночи, замеры не будут попадать в базу вообще.

sudo cp systemd/system-monitor.service systemd/system-monitor.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now system-monitor.timer

# Проверка
systemctl list-timers system-monitor.timer
journalctl -u system-monitor.service -n 20

Если раньше скрипт стоял в root-crontab — строку 0 */6 * * * …system_monitor.sh оттуда нужно убрать (sudo crontab -e), иначе опрос будет идти дважды.

Cron (альтернатива, без догоняния пропусков):

0 */6 * * * /home/esimych/esimych-docs/mytasks/system_monitor.sh >/dev/null 2>&1

Актуальный отчёт всегда доступен по стабильному пути system_reports/latest.html; предыдущие версии хранятся как system_reports/system_monitor-<timestamp>.html (не более REPORT_KEEP штук).

Режим dry-run: -n, --dry-run — диски, CPU и GPU опрашиваются, но запись в SQLite, init_db, HTML-отчёт (latest.html), ротация отчётов и уведомления не выполняются; вместо записей печатаются строки [dry-run] будет: запись в … . Критическое состояние в этом режиме не оценивается (оно считается по БД при формировании отчёта).


cp1251_to_utf8.sh

Рекурсивная перекодировка текстовых файлов из Windows-1251 в UTF-8.

Возможности:

  • Пропускает бинарные и уже UTF-8 файлы
  • Атомарная замена через временный файл (mktemp)
  • Сохраняет права и владельца файла
  • Режим --dry-run — только показывает, что будет перекодировано

Использование:

./cp1251_to_utf8.sh <каталог>
./cp1251_to_utf8.sh <каталог> --dry-run
./cp1251_to_utf8.sh -h

delete_by_ext.sh

Рекурсивное удаление файлов по расширению с логированием.

Опции:

Опция Описание
-d DIR Каталог для обработки (обязательно)
-e EXT Расширение без точки, например: tmp, log, bak
--dry-run Показать файлы без удаления
-h Справка

Использование:

./delete_by_ext.sh -d /tmp -e log --dry-run
./delete_by_ext.sh -d ./downloads -e tmp

Лог пишется в logs/delete_by_ext-YYYY-MM-DD-HH-MM-SS.jsonl; хранятся 10 последних JSONL-файлов.


doc2fb2.sh

Рекурсивная конвертация DOC / DOCX / RTF / ODT → FB2.

Цепочки конвертации:

  • docx, odt → pandoc → fb2
  • doc, rtf → LibreOffice → odt → pandoc → fb2
  • Fallback для rtf: pandoc напрямую, затем unrtf → txt → pandoc
  • Fallback для doc: antiword / catdoc / wvText → txt → pandoc

Ключевые опции:

Опция Описание
-d, --dir DIR Исходный каталог (обязательно)
-o, --out OUTDIR Выходной каталог
-n, --dry-run Без изменений, только показать
--overwrite Перезаписывать существующие FB2
--skip-newer Пропускать, если FB2 новее источника
--exclude PATTERN Исключить файлы по маске (можно несколько раз)
`--only doc docx
--author NAME Принудительно установить автора
--toc / --no-toc Добавить/убрать оглавление
`--log-level info error`

Использование:

./doc2fb2.sh -d /книги --dry-run
./doc2fb2.sh -d /книги -o /fb2 --overwrite --log-level error

Лог: JSONL-файл doc2fb2_*.jsonl. Логи старше 10 дней удаляются автоматически.


getip.sh

Определяет текущий внешний IP-адрес и ведёт его историю.

Возможности:

  • Запрашивает внешний IP у настраиваемого URL-сервиса через curl
  • Сохраняет текущий IP в файл
  • Добавляет запись с временной меткой, если полученного IP ещё нет в файле истории
  • Все пути и URL задаются через конфиг-файл conf/getip.conf

Конфигурация (conf/getip.conf):

Параметр По умолчанию
IP_SERVICE_URL https://icanhazip.com
IP_FILE /var/downloads/clouddata/nextcloud/work/ip.txt
IP_HISTORY_FILE /var/downloads/clouddata/nextcloud/work/ip_history.txt

Примеры совместимых сервисов: https://api.ipify.org, https://ifconfig.me/ip, https://checkip.amazonaws.com

Использование:

./getip.sh

Режим dry-run: -n, --dry-run — IP запрашивается, но IP_FILE и IP_HISTORY_FILE не создаются и не изменяются; печатается, что было бы записано.


hp_p1005_garuda.sh

Скрипт установки и настройки принтера HP LaserJet P1005 на Garuda Linux (Arch-based).

Возможности:

  • Устанавливает необходимые пакеты: hplip, system-config-printer, CUPS
  • Настраивает службы CUPS
  • Запуск без аргументов = --all (выполнить все шаги)
  • JSONL-логирование в файл hp_p1005_garuda_YYYY-MM-DD-HH-MM-SS.jsonl

Использование:

sudo ./hp_p1005_garuda.sh
sudo ./hp_p1005_garuda.sh --all

Режим dry-run: -n, --dry-run — проверки (пакеты, USB, CUPS) выполняются, а отключение configure-printer@, выгрузка и чёрный список usblp, запуск system-config-printer и тестовая печать только описываются строками [dry-run] будет: …. Без других аргументов, как и раньше, выполняется полный набор (--all).


lowercase_ext.sh

Переименовывает расширения файлов в нижний регистр (.JPG → .jpg, .MP3 → .mp3).

Режимы:

Режим Описание
--dry-run Показать, что будет переименовано
--apply Реально переименовать файлы

Использование:

./lowercase_ext.sh --dry-run /path/to/dir
./lowercase_ext.sh --apply   /path/to/dir

Показывает прогресс в процентах при обработке.


mht_to_fb2.py

Рекурсивно обрабатывает MHT/MHTML и CHM в указанном каталоге.

Возможности:

  • Умная автодетекция кодировки: UTF-8, CP1251, KOI8-R, Latin-1
  • Извлекает текст и структуру из HTML-части архива
  • MHT/MHTML конвертирует через pandoc в FB2 рядом с исходником; после успешной обычной конвертации удаляет исходный веб-архив
  • Для CHM не выполняет конвертацию, а удаляет одноимённый FB2, если он существует
  • --overwrite разрешает перезаписать существующий FB2
  • --dry-run не удаляет исходный MHT/MHTML и только имитирует удаление FB2 для CHM, но текущая реализация всё равно запускает pandoc и может записать FB2
  • JSONL-логирование в папку logs/

Требования: Python 3, pandoc

Использование:

python3 mht_to_fb2.py /архивы
python3 mht_to_fb2.py /архивы --overwrite
python3 mht_to_fb2.py /архивы --dry-run

music_downloader.sh

Обёртка для запуска Python-проекта music_downloader.

Порядок работы:

  1. Активирует виртуальное окружение (venv) в папке music_downloader/, если оно существует.
  2. Запускает music_downloader.py и сохраняет его код выхода.
  3. Деактивирует venv; при коде выхода загрузчика 0 запускает split_by_dash.sh, если он найден.
  4. Логирует результат и возвращает код выхода загрузчика.

Конфигурация загрузчика: в music_downloader/music_downloader.json общий непустой список download_paths задаётся в корне JSON, рядом с sites. Каждый трек любого моста сохраняется во все указанные папки. path задаёт каталог, max_size_mb — лимит его общего размера (по умолчанию 1024 МБ), а не квоту отдельного сайта. При переходе со старого формата перенесите общий список из сайта в корень JSON и удалите копии из sites: локальные настройки папок мостов больше не используются.

Папки и лимиты проверяются до обращения к сайтам и перед сохранением каждого трека. Размер проверяется в байтах, в том числе для каждого блока потока без Content-Length. Если трек не помещается хотя бы в одну папку, загрузчик удаляет его незавершённые .part, не записывает его в БД и завершает работу с кодом 2, без запуска сортировщика. Уже сохранённые треки остаются. Контроль рассчитан на один процесс загрузчика; параллельные записи других программ требуют квот файловой системы. Отсутствующий/пустой глобальный список, отсутствующая папка или уже достигнутый лимит также завершают запуск с кодом 2. Ошибки отдельных сайтов и треков логируются, но не обязательно меняют итоговый код выхода. При отсутствии файла конфигурации текущий загрузчик возвращает 0 без скачивания — подготовьте JSON до запуска обёртки. Пути сортировщика настраиваются отдельно.

Jamendo для категории / использует официальный API последних треков вместо HTML главной страницы, выбирая только записи с разрешённым скачиванием. Пустые результаты и ошибки явно логируются; подробности — в README загрузчика.

Мосты shazam_com (мировой Top 200; аудио ищется по точному совпадению в Sefon и LMusic) и mychords_net (новинки, аудио из встроенного плеера xpleer) получают ссылку на файл только перед скачиванием нового трека (resolve_track), поэтому уже скачанные позиции не вызывают лишних запросов.

Логи: в общей папке logs/ обёртка пишет music_downloader-<timestamp>.jsonl (оставляет последние 10 файлов), Python-движок — music_downloader_engine-<timestamp>.jsonl (очистка по глобальным log_cleanup_days и log_max_files, по умолчанию 10 дней и 10 файлов). Текстовый вывод загрузчика и сортировщика записывается в logs/cron_execution.log.

Использование:

./music_downloader.sh

Подробнее — в README загрузчика. Каталог music_downloader/ входит в этот репозиторий как обычная папка, без вложенного .git и без submodule. Код, драйверы, тесты и шаблон конфигурации версионируются вместе с mytasks; рабочий JSON, БД, логи и venv исключены правилами music_downloader/.gitignore. CI загрузчика находится в .github/workflows/music-downloader.yml.

Режим dry-run: -n, --dry-run — загрузчик music_downloader.py не запускается (venv не активируется), cron_execution.log не пишется; сортировщик запускается с --dry-run и показывает свой план.


replace_encoding.sh

Заменяет строку в текстовых файлах (поиск + замена через sed).

По умолчанию заменяет windows-1251 → utf-8 во всех текстовых файлах каталога. Если каталог не указан, используется текущий (.).

Опции:

Опция Описание
-d, --dry-run Показать файлы без изменений
-n, --no-recursive Только текущий каталог (без подкаталогов)
-h, --help Справка

Использование:

./replace_encoding.sh /path/to/dir
./replace_encoding.sh /path/to/dir "старая строка" "новая строка"
./replace_encoding.sh -d /path/to/dir

sources.sh

Синхронизация файлов на основе конфиг-файла conf/sources.conf с помощью rsync.

Формат конфига (каждая строка):

источник | назначение | rsync-аргументы

Опции:

Опция Описание
-c Указать альтернативный конфиг
-n Режим dry-run (передаёт --dry-run в rsync)
-E Разобрать кавычки в аргументах rsync через xargs, без выполнения eval
-h Справка

Логи: JSONL-файл logs/sources_YYYY-MM-DD-report.jsonl. Логи старше 10 дней удаляются автоматически.

Использование:

./sources.sh
./sources.sh -n
./sources.sh -c /path/to/my.conf

split_by_dash.sh

Раскладывает файлы по папкам-исполнителям по шаблону Исполнитель - Название.

Алгоритм:

  • Ищет - (или –, —) в имени файла

  • Часть до разделителя становится именем подпапки

  • Файл перемещается в эту подпапку; при конфликте имён добавляет суффикс .1, .2…

  • Спорные слияния проверяются. Если у папок группы совпадает «мягкий» ключ (без пробелов, _, +, &, ,, апострофов, !, ? и точек в конце слов), группа объединяется сразу. Иначе (точки между буквами, дефисы: G.A.M.E / Game, Jay-Z / Jay z, K-Maro / Kmaro) слияние сначала проверяется по файлам: у всех папок должен быть общий тег исполнителя (artist/album_artist, до 5 файлов на папку, нужен ffprobe). Если файлы не подтвердили, слияние проверяется по официальным источникам (при OFFICIAL_NAMES=yes): у всех папок (по имени и тегам) должен быть общий артист MusicBrainz (имя или псевдоним совпадает точно); тогда папка получает официальное имя артиста (G.A.M.E + Game → The Game). Если артисты разные или подтвердить нечем, слияние отклоняется (строка Внимание: спорное слияние отклонено …, событие merge_rejected). Решения кэшируются в state/split_by_dash_verdicts.tsv (ключ<TAB>merge|keep<TAB>причина); строку можно поправить вручную, чтобы принудительно объединить или не объединять группу.

  • Имена, безопасные для Windows (файлы синхронизируются Syncthing между Linux и Windows, недопустимые там не синхронизируются). Применяется к создаваемым папкам, перемещаемым файлам и ко всем существующим записям внутри обработанных каталогов и DEST_DIR на любой глубине:

    • символы < > : " \ | ? * и управляющие заменяются на _;
    • & без пробелов с обеих сторон получает их, и в папках, и в файлах: 10AGE&Анет Сай → 10AGE & Анет Сай, Kaskade&BUNT - Song.mp3 → Kaskade & BUNT - Song.mp3, A&B&C → A & B & C (если пробел есть хотя бы с одной стороны, имя не меняется);
    • ведущие пробелы, конечные точки и пробелы удаляются: папка Автор., часть Автор. в Автор. - песня.mp3. и конечная точка после mp3 (при раскладке файла из корня) → Автор/Автор - песня.mp3; Fred again.. → Fred again;
    • зарезервированные имена CON, PRN, AUX, NUL, COM1-9, LPT1-9 (любой регистр, любое расширение) получают префикс _ (CON.mp3 → _CON.mp3);
    • переименованная папка, совпавшая с существующей без учёта регистра, объединяется с ней; файл при точном совпадении имени получает суффикс .1, .2…;
    • файлы в одном каталоге, различающиеся только регистром имени (Windows хранит только один из них): остаётся больший по размеру, меньшие удаляются (при равном размере остаётся первый по кодовым точкам, заглавные раньше строчных); каждое удаление пишется в лог событием case_dup_removed с размерами, в dry-run — строкой [dry-run] будет: Удалён файл меньшего размера…. Папки глубже первого уровня, различающиеся только регистром, лишь выводятся строкой Внимание: … и записью case_collision (содержимое автоматически не объединяется);
    • ограничения длины отдельно не применяются: лимит Linux (255 байт) строже лимита Windows (255 символов UTF-16).
    • исключения: имена из conf/split_by_dash.keep.conf (точное совпадение имени файла или папки, по одному на строку, # — комментарий; образец — conf/split_by_dash.keep.conf.example) скрипт не переименовывает вообще, например VashKevich&Olisha - Вызывай скорую.mp3. Существующие имена файлов вида Автор. - песня.mp3 внутри папок не переименовываются (точка в середине имени допустима в Windows).
  • Папки, различающиеся только регистром, знаками, пробелами, &, апострофами и подчёркиваниями (в том числе Имя Фамилия с пробелами и кириллицей), объединяются, причём каждый каталог обрабатывается независимо: сравниваются только прямые подпапки внутри одного каталога, вложенные папки (альбомы внутри исполнителя) и другие каталоги (например, Music при обработке _cloud) не учитываются. Ключ сравнения — нижний регистр и только буквы/цифры (как normalize_db_text в music_downloader): 10AGE Анет Сай = 10AGE&Анет Сай, Ағайындылар тобы & Айгерім Битанова = Ағайындылар Тобы Айгерім Битанова, Банд'Эрос = Банд\Эрос=БандЭрос, Dua_Lipa_=Dua Lipa. Порядок слов не учитывается, поэтому A & BиB & A` — разные папки. Эталон выбирается так:

    • для файла Исполнитель - Название в корне — папка с регистром исполнителя из имени файла (нет — создаётся), затем общий проход ниже;
    • официальное имя (если OFFICIAL_NAMES=yes): из MusicBrainz (musicbrainz.org/ws/2/artist, не чаще раза в секунду) принимается артист с оценкой ≥ 90, чьё официальное имя даёт тот же ключ (например, Банд’Эрос с типографским апострофом, ABBA, AC/DC → AC_DC); папка создаётся, даже если среди вариантов её нет. Результаты и отрицательные ответы кэшируются в state/split_by_dash_official.tsv (ключ<TAB>имя, - = не найдено; файл можно править вручную, чтобы задать имя самому); при сетевой ошибке в кэш ничего не пишется. Нужны curl и jq. Для коллабораций (A & B) в MusicBrainz записей нет — работает правило ниже;
    • иначе лучший из существующих вариантов, по порядку: 1) не «КАПСОМ» целиком (HURTS проигрывает Hurts); 2) без _/+ (следы замены знаков: Dua_Lipa_ проигрывает Dua Lipa); 3) приоритет &: & → & → , → прочее; 4) больше файлов; 5) ближе к «Первая заглавная, остальные строчные»; 6) первая по кодовым точкам.

    Содержимое остальных папок переносится в эталонную (конфликты имён — суффикс .1, .2…), остальные папки удаляются. Регистр и знаки сравниваются через awk в UTF-8 (нужен gawk; NFKC-нормализации нет). Перенос в DEST_DIR сливает только папки с точно совпадающим именем; нечёткое слияние DEST_DIR выполняется отдельным проходом после переноса. Режим --dry-run тоже обращается к MusicBrainz и пишет кэш (state/), но папки и файлы не меняет.

  • Имена, безопасные для Windows (файлы синхронизируются Syncthing между Linux и Windows, недопустимые там не синхронизируются). Применяется к создаваемым папкам, перемещаемым файлам и ко всем существующим записям внутри обработанных каталогов и DEST_DIR на любой глубине:

    • символы < > : " \ | ? * и управляющие заменяются на _;
    • ведущие пробелы, конечные точки и пробелы удаляются: папка Автор., часть Автор. в Автор. - песня.mp3. и конечная точка после mp3 (при раскладке файла из корня) → Автор/Автор - песня.mp3; Fred again.. → Fred again;
    • зарезервированные имена CON, PRN, AUX, NUL, COM1-9, LPT1-9 (любой регистр, любое расширение) получают префикс _ (CON.mp3 → _CON.mp3);
    • переименованная папка, совпавшая с существующей без учёта регистра, объединяется с ней; файл при точном совпадении имени получает суффикс .1, .2…;
    • файлы в одном каталоге, различающиеся только регистром имени (Windows хранит только один из них): остаётся больший по размеру, меньшие удаляются (при равном размере остаётся первый по кодовым точкам, заглавные раньше строчных); каждое удаление пишется в лог событием case_dup_removed с размерами, в dry-run — строкой [dry-run] будет: Удалён файл меньшего размера…. Папки глубже первого уровня, различающиеся только регистром, лишь выводятся строкой Внимание: … и записью case_collision (содержимое автоматически не объединяется);
    • ограничения длины отдельно не применяются: лимит Linux (255 байт) строже лимита Windows (255 символов UTF-16). Существующие имена файлов вида Автор. - песня.mp3 внутри папок не переименовываются (точка в середине имени допустима в Windows).
  • Папки, различающиеся только регистром (в том числе Имя Фамилия с пробелами и кириллицей, например ИВАН ПЕТРОВ / иван петров), объединяются, причём каждый каталог обрабатывается независимо: сравниваются только прямые подпапки внутри одного каталога, вложенные папки (альбомы внутри исполнителя) и другие каталоги (например, Music при обработке _cloud) не учитываются. Два правила эталона:

    • для файла Исполнитель - Название эталон — папка с регистром исполнителя из имени файла (нет — создаётся);
    • для остальных дубликатов (и в DEST_DIR после переноса, отдельным проходом) эталон выбирается по порядку: 1) не «КАПСОМ» целиком (HURTS проигрывает Hurts даже при большем числе файлов); 2) больше файлов; 3) ближе к виду «Первая заглавная, остальные строчные» (первая буква заглавная, меньше прочих заглавных: In hoode, MD Dj&Olivia, Alisher Konysbaev); 4) первая по кодовым точкам.

    Содержимое остальных папок переносится в эталонную (конфликты имён — суффикс .1, .2…), остальные папки удаляются. Регистр сравнивается через awk tolower() в UTF-8 (нужен gawk). Перенос в DEST_DIR сливает только папки с точно совпадающим именем.

Источники каталогов:

  1. Аргумент командной строки: ./split_by_dash.sh /путь/к/папке (несуществующий каталог — ошибка с кодом 2, без перехода к конфигу). DEST_DIR из конфига при явном каталоге не применяется; целевой каталог задаётся ключом --dest КАТАЛОГ
  2. Конфиг-файл conf/split_by_dash.conf (список папок, по одной на строку)

Перенос результата (опционально):

  • Строка DEST_DIR=/путь/к/каталогу в conf/split_by_dash.conf задаёт каталог, куда переносятся все получившиеся папки-исполнители после разбора
  • Если папки с таким именем в DEST_DIR ещё нет — она переносится целиком
  • Если такая папка уже существует — файлы объединяются в неё (конфликты имён также разрешаются суффиксом .1, .2…), а опустевший источник удаляется
  • Без DEST_DIR папки остаются внутри исходного каталога
  • Все операции переноса/объединения логируются

Логи: JSONL-файл в logs/. Хранятся 10 последних файлов.

Опции:

Опция Описание
-n, --dry-run Только показать план (создание папок, перемещение файлов, объединение папок разного регистра, перенос в DEST_DIR) строками [dry-run] будет: …; файловая система не меняется. В лог те же действия пишутся событиями dry_<event>. Для папок, которые в плане ещё не созданы, содержимое при переносе в DEST_DIR не перечисляется — файлы видны в строках раскладки
--dest КАТАЛОГ Целевой каталог результата при запуске с явным каталогом (DEST_DIR из конфига действует только в режиме по списку из конфига)
-h, --help Справка

Использование:

./split_by_dash.sh /path/to/music
./split_by_dash.sh --dry-run /path/to/music
./split_by_dash.sh   # использует conf/split_by_dash.conf
./split_by_dash.sh -n   # проверка по конфигу без изменений

unpack-fb2zip.sh

Распаковывает архивы *.fb2.zip, извлекая .fb2-файлы в тот же каталог.

Возможности:

  • Рекурсивный поиск *.fb2.zip
  • Конфликты имён разрешаются суффиксами _1, _2…
  • Режим --dry-run
  • JSONL-логирование

Опции:

Опция Описание
-p, --path DIR Корневая папка для поиска (обязательно)
--dry-run Только показать действия
--log FILE Путь к JSONL-логу
-h, --help Справка

Использование:

./unpack-fb2zip.sh -p /книги
./unpack-fb2zip.sh -p /книги --dry-run

Зависимости

Скрипт Основные зависимости
all_to_mp3.sh ffmpeg, ffprobe, find, xargs
auto_delete_old_files.sh POSIX shell, find, sort, awk, sed
btrfs_monitor.sh Bash, btrfs, findmnt, awk; опц.: notify-send
check-store-mount.sh Bash, mountpoint, timeout; опц.: notify-send
check_backups.sh Bash, find, sort, timeout; опц.: notify-send, zstd/gzip/unzip/7z/tar (проверка целостности)
cloud_backup.sh Bash, wg-quick, sshpass, ssh; удалённо: docker, tar, один из zstd/pigz/gzip
cloud_backup_qnap.sh Entware Bash, wireguard-go, ncat, xxd, ssh; удалённо: docker, tar, nc, один из zstd/pigz/gzip
cp1251_to_utf8.sh iconv, file, find
delete_by_ext.sh find
doc2fb2.sh pandoc, libreoffice; опц.: unrtf, antiword, catdoc, wvText
getip.sh curl
hp_p1005_garuda.sh Arch/Garuda, pacman, lsusb, systemctl, CUPS (lp), system-config-printer
lowercase_ext.sh Bash, find, mv
mht_to_fb2.py Python 3, pandoc
music_downloader.sh Bash, Python 3, локальный проект music_downloader/; после успеха — split_by_dash.sh
replace_encoding.sh Bash, grep, sed
sources.sh POSIX shell, rsync, xargs
split_by_dash.sh POSIX shell, find, mv, sed, gawk
system_monitor.sh Bash, smartctl (root), jq, sqlite3, lsblk; опц.: sensors, nvidia-smi, notify-send
unpack-fb2zip.sh Bash, find, unzip

Тесты и статический анализ

Полные команды (установка bats-core, запуск отдельного набора, ShellCheck, python3 -m py_compile и т.д.) и правила именования — в разделе «Валидация» ai-md/AI.md, не дублируются здесь.

Кратко: tests/ — по Bats-файлу на актуальный корневой скрипт плюс общий all_scripts_syntax.bats (синтаксис каждого *.sh по шебангу); внешние команды стабируются через временный PATH (для system_monitor.sh настоящими остаются sqlite3 и jq). CI — два независимых workflow: .github/workflows/bats-tests.yml (все Bats-тесты на каждый push/PR) и .github/workflows/shellcheck.yml (ShellCheck error+style, синтаксис bash -n/sh -n, сканер небракетированных $VAR, py_compile — только при изменении *.sh/*.py).

About

рабочие скрипты

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages