diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..14f8b2f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.14) + +project(snag) +set(PROJECT_VERSION_MAJOR 0) +set(PROJECT_VERSION_MINOR 1) +set(PROJECT_VERSION_PATCH 0) +string(TIMESTAMP PROJECT_VERSION_BUILD "%y%j.%H%M") + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +include(snag_git) +include(snag_platforms) +set(PROJECT_VERSION_SUFFIX "-${GIT_VERSION_SUFFIX}") + +# Overwrite suffix for Release or Release Candidate builds +set(PROJECT_VERSION_SUFFIX "-alpha.1") +set(CMAKE_BUILD_TYPE "Release") +if(CMAKE_BUILD_TYPE) + set(BUILD_TYPE ${CMAKE_BUILD_TYPE}) +else() + set(BUILD_TYPE "Debug") +endif() + +set(PROJECT_VERSION_CODENAME ${OS_RELEASE_VERSION_CODENAME}) +set(PROJECT_VERSION_DISPLAY "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.${PROJECT_VERSION_BUILD}") +message(STATUS "PROJECT_VERSION_CODENAME: ${PROJECT_VERSION_CODENAME}") +message(STATUS "PROJECT_VERSION_DISPLAY: ${PROJECT_VERSION_DISPLAY}") +message(STATUS "") + +# Deb package +set(DEB_PACKAGE_VERSION "${PROJECT_VERSION_DISPLAY}${PROJECT_VERSION_SUFFIX}") +if(PROJECT_VERSION_CODENAME) + set(DEB_PACKAGE_VERSION "${DEB_PACKAGE_VERSION}-${PROJECT_VERSION_CODENAME}") +endif() +set(DEB_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") +string(TIMESTAMP DEB_PACKAGE_CHANGELOG_DATE "%a, %d %b %Y %H:%M:%S +0000") + +configure_file( + "${CMAKE_SOURCE_DIR}/debian/changelog.in" + "${CMAKE_SOURCE_DIR}/debian/changelog" +) + +configure_file( + "${CMAKE_SOURCE_DIR}/source/snag/version_.d.in" + "${CMAKE_SOURCE_DIR}/source/snag/version_.d" +) + +message(STATUS "DEB_PACKAGE_NAME: ${DEB_PACKAGE_NAME}") +message(STATUS "DEB_PACKAGE_VERSION: ${DEB_PACKAGE_VERSION}") +message(STATUS "") + +file(MAKE_DIRECTORY "${CMAKE_SOURCE_DIR}/bin") + +add_custom_command( + OUTPUT "${CMAKE_SOURCE_DIR}/bin/${CMAKE_PROJECT_NAME}" + COMMAND build dlang ldc --output "${CMAKE_SOURCE_DIR}/bin" --project "${CMAKE_SOURCE_DIR}" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + COMMENT "Building snag with dub" +) + +add_custom_target(snag ALL + DEPENDS "${CMAKE_SOURCE_DIR}/bin/${CMAKE_PROJECT_NAME}" +) + +include(snag_patch) + +add_dependencies(snag apply_config_patch) + +install( + FILES "${CMAKE_SOURCE_DIR}/bin/${CMAKE_PROJECT_NAME}" + DESTINATION bin + PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ +) + +install( + FILES "${CMAKE_SOURCE_DIR}/files/snag.json.bak" + DESTINATION /etc/snag/ + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ +) + +install( + FILES "${CMAKE_SOURCE_DIR}/files/snag.8" + DESTINATION /usr/share/man/ru/man8/ + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ +) diff --git a/cmake/snag_git.cmake b/cmake/snag_git.cmake new file mode 100644 index 0000000..dee20ed --- /dev/null +++ b/cmake/snag_git.cmake @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.6) + +if(DEFINED ENV{CI_COMMIT_BRANCH}) + string(REGEX REPLACE ".*/" "" GIT_BRANCH $ENV{CI_COMMIT_BRANCH}) + string(REPLACE "_" "-" GIT_BRANCH ${GIT_BRANCH}) + if(NOT GIT_BRANCH STREQUAL "stable") + set(GIT_VERSION_SUFFIX "${GIT_BRANCH}-") + else() + set(GIT_VERSION_SUFFIX "") + endif() +else() + set(GIT_BRANCH "dev") + set(GIT_VERSION_SUFFIX "${GIT_BRANCH}-") +endif() + +if(DEFINED ENV{CI_COMMIT_SHORT_SHA}) + set(GIT_TAG $ENV{CI_COMMIT_SHORT_SHA}) +else() + set(GIT_TAG "0000000") +endif() + +set(GIT_VERSION_SUFFIX "${GIT_VERSION_SUFFIX}${GIT_TAG}") +MESSAGE(STATUS "GIT_VERSION_SUFFIX: ${GIT_VERSION_SUFFIX}") +MESSAGE(STATUS "") diff --git a/cmake/snag_patch.cmake b/cmake/snag_patch.cmake new file mode 100644 index 0000000..8e438da --- /dev/null +++ b/cmake/snag_patch.cmake @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.6) + +find_program(PATCH_EXECUTABLE patch) +if(NOT PATCH_EXECUTABLE) + message(FATAL_ERROR "Patch utility not found! Install patch tool before continuing.") +endif() + +set(PATCH_FILE "${CMAKE_SOURCE_DIR}/patches/config.patch") +set(PATCH_MARKER_FILE "${CMAKE_BINARY_DIR}/.config_patch_applied") + +if(NOT EXISTS "${PATCH_FILE}") + message(WARNING "Patch file not found: ${PATCH_FILE}") +endif() + +add_custom_command( + OUTPUT "${PATCH_MARKER_FILE}" + COMMAND "${PATCH_EXECUTABLE}" -p1 -i "${PATCH_FILE}" --reject-file=- --silent || exit 0 + COMMAND cmake -E touch "${PATCH_MARKER_FILE}" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + COMMENT "Applying config.patch..." + VERBATIM +) + +add_custom_target(apply_config_patch + DEPENDS "${PATCH_MARKER_FILE}" +) diff --git a/cmake/snag_platforms.cmake b/cmake/snag_platforms.cmake new file mode 100644 index 0000000..5608fef --- /dev/null +++ b/cmake/snag_platforms.cmake @@ -0,0 +1,59 @@ +cmake_minimum_required (VERSION 3.6) + +if (NOT WIN32) + set (OS_RELEASE_FILE "/etc/os-release") + file(STRINGS ${OS_RELEASE_FILE} OS_RELEASE) +else () + list(APPEND OS_RELEASE "ID=${CMAKE_SYSTEM_NAME}") +endif () + +foreach(_line IN LISTS OS_RELEASE) + if (_line STREQUAL "") + continue() + endif() + string(REPLACE "=" ";" _pair "${_line}") + list(GET _pair 0 _name) + list(GET _pair 1 _value) + set(OS_RELEASE_${_name} ${_value}) +endforeach() + +if (${OS_RELEASE_ID} MATCHES "altlinux") + string(REGEX REPLACE "[\"\.]" "" alt_branch_id ${OS_RELEASE_ALT_BRANCH_ID}) + set (OS_RELEASE_VERSION_CODENAME "${alt_branch_id}") +elseif (${OS_RELEASE_ID} MATCHES "astra") + #string(REGEX REPLACE "_x86-64$" "" _version ${OS_RELEASE_VERSION_CODENAME}) + file(STRINGS "/etc/astra_version" _version) + string(REGEX REPLACE "\\.|\t|\n|\r" "" _version ${_version}) + set (OS_RELEASE_VERSION_CODENAME "${OS_RELEASE_VERSION_CODENAME}") + if (${OS_RELEASE_VERSION_CODENAME} MATCHES "4.7_arm") + set (OS_RELEASE_VERSION_CODENAME "astra47") + elseif (NOT ${OS_RELEASE_VERSION_CODENAME} MATCHES "orel") + set (OS_RELEASE_VERSION_CODENAME "${OS_RELEASE_ID}${_version}") + else () + set (OS_RELEASE_VERSION_CODENAME "${OS_RELEASE_VERSION_CODENAME}") + endif() +elseif (${OS_RELEASE_ID} MATCHES "debian") + set (OS_RELEASE_VERSION_CODENAME "${OS_RELEASE_VERSION_CODENAME}") +elseif (${OS_RELEASE_ID} MATCHES "centos") + string(REGEX REPLACE "[\"\.]" "" version_id ${OS_RELEASE_VERSION_ID}) + set (OS_RELEASE_VERSION_CODENAME "el${version_id}") +elseif (${OS_RELEASE_ID} MATCHES "fedora") + set (OS_RELEASE_VERSION_CODENAME "f${OS_RELEASE_VERSION_ID}") +elseif (${OS_RELEASE_ID} MATCHES "freebsd") + set (OS_RELEASE_VERSION_CODENAME "${OS_RELEASE_NAME}") +elseif (${OS_RELEASE_ID} MATCHES "msys2") + set (OS_RELEASE_VERSION_CODENAME "${OS_RELEASE_ID}") +elseif (${OS_RELEASE_ID} MATCHES "ubuntu") + set (OS_RELEASE_VERSION_CODENAME "${OS_RELEASE_UBUNTU_CODENAME}") +elseif (${OS_RELEASE_ID} MATCHES "sberlinux") + string(REGEX REPLACE "[\"\.]" "" version_id ${OS_RELEASE_VERSION_ID}) + set (OS_RELEASE_VERSION_CODENAME "sl${version_id}") +elseif (${OS_RELEASE_ID} MATCHES "Windows") + set (OS_RELEASE_VERSION_CODENAME "win") +else () + set (OS_RELEASE_VERSION_CODENAME "") +endif() + +MESSAGE(STATUS "OS_RELEASE_ID: ${OS_RELEASE_ID}") +MESSAGE(STATUS "OS_RELEASE_VERSION_CODENAME: ${OS_RELEASE_VERSION_CODENAME}") +MESSAGE(STATUS "") diff --git a/debian.md b/debian.md new file mode 100644 index 0000000..8f287aa --- /dev/null +++ b/debian.md @@ -0,0 +1,6 @@ +# Сборка из под Debian + +``` +cmake . +dpkg-buildpackage -us -uc -nc +``` diff --git a/debian/.gitignore b/debian/.gitignore new file mode 100644 index 0000000..14331c5 --- /dev/null +++ b/debian/.gitignore @@ -0,0 +1,10 @@ +.debhelper +*.debhelper +*.debhelper.log +changelog +debhelper-build-stamp +termidesk-lsb +termidesk-lsb.substvars +files +tmp + diff --git a/debian/changelog.in b/debian/changelog.in new file mode 100644 index 0000000..962b128 --- /dev/null +++ b/debian/changelog.in @@ -0,0 +1,6 @@ +${DEB_PACKAGE_NAME} (${DEB_PACKAGE_VERSION}) stable; urgency=medium + + * Release snag ${DEB_PACKAGE_VERSION} + + -- Alexander Zhirov ${DEB_PACKAGE_CHANGELOG_DATE} + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..b4de394 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +11 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..a4d4abd --- /dev/null +++ b/debian/control @@ -0,0 +1,14 @@ +Source: snag +Maintainer: Alexander Zhirov +Section: non-free/admin +Priority: optional +Build-Depends: debhelper (>= 11), cmake +Homepage: https://zhirov.kz + +Package: snag +Architecture: any +Section: non-free/admin +Priority: optional +Description: Сommand-line utility for creating, managing, and restoring data snapshots +Depends: git (>= 2.30) +Homepage: https://zhirov.kz diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..1da37a9 --- /dev/null +++ b/debian/rules @@ -0,0 +1,23 @@ +#!/usr/bin/make -f + +BUILD_SYSTEM = --buildsystem=cmake +DEBIAN_DIR = $(CURDIR)/debian +DEST_DIR = $(DEBIAN_DIR)/snag + +%: + dh $@ $(BUILD_SYSTEM) + +override_dh_auto_configure: + cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr + +override_dh_auto_build: + cmake --build build + +override_dh_auto_test: + # Ничего не делаем, тесты отсутствуют + +override_dh_auto_install: + DESTDIR=$(DEST_DIR) cmake --install build + +override_dh_shlibdeps: + # Отключить проверку зависимостей от *.so diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/files/snag.8 b/files/snag.8 new file mode 100644 index 0000000..0ee9aa2 --- /dev/null +++ b/files/snag.8 @@ -0,0 +1,324 @@ +.\" Man-страница для snag +.\" Сгенерирована из документации Markdown +.TH SNAG 1 "30 мая 2025" "1.0" "Команды пользователя" +.SH НАЗВАНИЕ +snag \- утилита для создания, управления и восстановления снимков данных +.SH СИНТАКСИС +.B snag +[\fIфлаги\fR] [\fIопции\fR] \fIкоманда\fR +.SH ОПИСАНИЕ +.B snag +(snapshot git) — это утилита командной строки для создания, управления и восстановления снимков данных. Она позволяет импортировать, экспортировать, создавать и восстанавливать снимки, а также управлять правилами отслеживания файлов. +.SH ФЛАГИ +.TP +.BR \-h ", " \-\-help +Выводит справку. +.TP +.BR \-\-version +Выводит версию утилиты. +.SH ОПЦИИ +.TP +.BR \-c ", " \-\-config " <путь>" +Указывает путь к файлу конфигурации. +.SH КОМАНДЫ +.TP +.B init +Инициализация репозитория для хранения снимков. +.RS +.B snag init +[\-h] [\-f] +.TP +.BR \-f ", " \-\-force +Перезаписывает существующий репозиторий. +.RE +.TP +.B create +Создание нового снимка. +.RS +.B snag create +[\-h] [\-\-no\-pre] [\-\-no\-post] [\-c <комментарий>] [\-a <автор>] [\-e ] +.TP +.BR \-\-no\-pre +Выполнение без предкоманд. +.TP +.BR \-\-no\-post +Выполнение без посткоманд. +.TP +.BR \-c ", " \-\-comment " <значение>" +Указывает комментарий к снимку. +.TP +.BR \-a ", " \-\-author " <значение>" +Указывает автора снимка. +.TP +.BR \-e ", " \-\-email " <значение>" +Указывает email автора. +.RE +.TP +.B import +Импорт снимка из архива tar.gz. +.RS +.B snag import +[\-h] [\-\-no\-pre] [\-\-no\-post] [\-c <комментарий>] [\-a <автор>] [\-e ] <путь_к_архиву> +.TP +.BR \-\-no\-pre +Без выполнения предкоманд. +.TP +.BR \-\-no\-post +Без выполнения посткоманд. +.TP +.BR \-c ", " \-\-comment " <значение>" +Комментарий к снимку. +.TP +.BR \-a ", " \-\-author " <значение>" +Автор снимка. +.TP +.BR \-e ", " \-\-email " <значение>" +Email автора. +.TP +.BR <путь_к_архиву> +Путь к файлу tar.gz. +.RE +.TP +.B export +Экспорт снимка в архив tar.gz. +.RS +.B snag export +[\-h] [\-s <хэш_снимка>] <путь_к_папке> +.TP +.BR \-s ", " \-\-snapshot " <хэш>" +Указывает хэш снимка. +.TP +.BR <путь_к_папке> +Путь к папке для сохранения архива. +.RE +.TP +.B restore +Восстановление состояния из указанного снимка. +.RS +.B snag restore +[\-h] [\-\-no\-pre] [\-\-no\-post] <хэш_снимка> +.TP +.BR \-\-no\-pre +Без выполнения предкоманд. +.TP +.BR \-\-no\-post +Без выполнения посткоманд. +.TP +.BR <хэш_снимка> +Хэш восстанавливаемого снимка. +.RE +.TP +.B list +Вывод списка снимков. +.RS +.B snag list +[\-h] [\-c] [\-a] [\-e] +.TP +.BR \-c ", " \-\-comment +Показать комментарии к снимкам. +.TP +.BR \-a ", " \-\-author +Показать авторов снимков. +.TP +.BR \-e ", " \-\-email +Показать email авторов. +.RE +.TP +.B diff +Показ изменённых данных. +.RS +.B snag diff +.RE +.TP +.B status +Проверка статуса отслеживаемых файлов. +.RS +.B snag status +.RE +.TP +.B size +Отображение размера снимков. +.RS +.B snag size +.RE +.TP +.B rules +Управление правилами отслеживания. +.RS +.B snag rules +[\-h] save|show|update|reset|clear +.TP +.B save +Сохраняет правила. +.TP +.B show +Показывает правила. +.RS +.B snag rules show +[\-h] [\-c] +.TP +.BR \-c ", " \-\-config +Показать правила из файла конфигурации. +.RE +.TP +.B update +Обновляет правила. +.RS +.B snag rules update +[\-h] [\-r] +.TP +.BR \-r ", " \-\-remove +Удаляет игнорируемые файлы из отслеживания (требуется осторожность). +.RE +.TP +.B reset +Сбрасывает правила до состояния внесенных изменений. +.TP +.B clear +Очищает правила. +.RE +.SH ПРИМЕРЫ +.TP +Инициализация репозитория: +.RS +.B snag init +.RE +.TP +Создание снимка с комментарием: +.RS +.B snag create -c \(dqНачальный снимок\(dq -a \(dqИван Иванов\(dq -e \(dqivan@example.com\(dq +.RE +.TP +Импорт снимка из архива: +.RS +.B snag import archive.tar.gz +.RE +.TP +Экспорт снимка: +.RS +.B snag export -s abc123 /path/to/output +.RE +.TP +Восстановление снимка: +.RS +.B snag restore abc123 +.RE +.TP +Просмотр списка снимков с комментариями: +.RS +.B snag list -c +.RE +.TP +Обновление правил отслеживания: +.RS +.B snag rules update +.RE +.SH КОНФИГУРАЦИЯ +.B snag +поддерживает настройку через конфигурационный файл в формате JSON, который задаётся с помощью опции +.B -c +или +.B --config. +Этот файл определяет параметры работы утилиты, включая пути, автора, команды для выполнения до и после создания снимков, а также правила отслеживания файлов. + +Пример использования конфигурационного файла: +.RS +.B snag -c /path/to/config.json create +.RE + +.SS Пример конфигурационного файла +.nf +{ + "git": "/path/to/git/repository/dir", + "project": "/path/to/project", + "email": "user@site.domain", + "author": "snag", + "presnag": [ + "systemctl stop my.service" + ], + "postsnag": [ + "systemctl start my.service" + ], + "rules": { + "tracking": [ + "/first_dir/", + "/second_dir/*.conf", + "/second_dir/more/" + ], + "ignore": [ + "/second_dir/more/*.so" + ] + } +} +.fi + +.SS Описание полей конфигурационного файла +.TP +.B git (строка) +Путь к репозиторию для хранения снимков (например, /path/to/git/repository/dir). +.TP +.B project (строка) +Путь к проекту или директории, файлы которой отслеживаются (например, /path/to/project). +.TP +.B email (строка) +Email автора снимков (например, user@site.domain). Переопределяется опцией +.B -e +или +.B --email. +.TP +.B author (строка) +Имя автора снимков (например, snag). Переопределяется опцией +.B -a +или +.B --author. +.TP +.B presnag (массив строк) +Список команд, выполняемых перед созданием или импортом снимка (если не указан +.B --no-pre). +Пример: systemctl stop my.service. +.TP +.B postsnag (массив строк) +Список команд, выполняемых после создания или импорта снимка (если не указан +.B --no-post). +Пример: systemctl start my.service. +.TP +.B rules (объект) +Определяет правила отслеживания файлов. +.RS +.TP +.B tracking (массив строк) +Список шаблонов файлов или директорий для отслеживания (например, /first_dir/, /second_dir/*.conf, /second_dir/more/). +.TP +.B ignore (массив строк) +Список файлов или директорий, исключаемых из отслеживания (например, /second_dir/more/*.so). +.RE +.SS Примечания +.IP \(bu 4 +Правила в разделе +.B rules +можно обновлять с помощью +.B snag rules update +или просматривать с помощью +.B snag rules show -c. +.IP \(bu 4 +Параметры командной строки, такие как +.B -e +или +.B -a, +имеют приоритет над значениями из конфигурационного файла. +.IP \(bu 4 +Убедитесь, что пути, указанные в +.B git +и +.B project, +существуют и доступны для записи/чтения. +.IP \(bu 4 +Команды в +.B presnag +и +.B postsnag +должны быть корректными и доступными в системе, иначе операция может завершиться ошибкой. +.SH ЛИЦЕНЗИЯ +GPL-2.0. Подробности см. в файле LICENSE. +.SH КОНТАКТЫ +Для вопросов и предложений: alexander@zhirov.kz \ No newline at end of file diff --git a/files/snag.json.bak b/files/snag.json.bak new file mode 100644 index 0000000..4cf4d52 --- /dev/null +++ b/files/snag.json.bak @@ -0,0 +1,16 @@ +{ + "git": "", + "project": "", + "email": "", + "author": "", + "presnag": [ + ], + "postsnag": [ + ], + "rules": { + "tracking": [ + ], + "ignore": [ + ] + } +} diff --git a/patches/config.patch b/patches/config.patch new file mode 100644 index 0000000..d436ffe --- /dev/null +++ b/patches/config.patch @@ -0,0 +1,13 @@ +diff --git a/source/app.d b/source/app.d +index a79b3f8..2872378 100644 +--- a/source/app.d ++++ b/source/app.d +@@ -148,7 +148,7 @@ int main(string[] args) + ) + .parse(args); + +- string configFile = argumets.option("config", "snag.json"); ++ string configFile = argumets.option("config", "/etc/snag/snag.json"); + + SnagConfig config; + diff --git a/source/snag/version_.d.in b/source/snag/version_.d.in new file mode 100644 index 0000000..9a8338d --- /dev/null +++ b/source/snag/version_.d.in @@ -0,0 +1,3 @@ +module snag.version_; + +enum snagVersion = "${PROJECT_VERSION_DISPLAY}";