├── tmp.cfe ├── html ├── img │ ├── git.png │ ├── error.png │ └── success.png └── index.html ├── doc └── img │ ├── 687477.png │ └── browser_KnoJenu9An.jpg ├── nssm-2.24 ├── win32 │ └── nssm.exe ├── win64 │ └── nssm.exe ├── ChangeLog.txt └── README.txt ├── .idea ├── vcs.xml ├── misc.xml ├── .gitignore ├── modules.xml ├── 1C2GIT.iml └── highlightedFiles.xml ├── Example ├── MapUsers.conf └── Config.conf ├── CreateService.bat ├── .golangci.yaml ├── .github ├── workflows │ └── go.yml └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── go.mod ├── CODE_OF_CONDUCT.md ├── Confs └── settings.go ├── README.md ├── go.sum ├── Git └── git.go ├── main.go ├── LICENSE └── Configuration └── conf.go /tmp.cfe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazarenkoA/1C2GIT/HEAD/tmp.cfe -------------------------------------------------------------------------------- /html/img/git.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazarenkoA/1C2GIT/HEAD/html/img/git.png -------------------------------------------------------------------------------- /doc/img/687477.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazarenkoA/1C2GIT/HEAD/doc/img/687477.png -------------------------------------------------------------------------------- /html/img/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazarenkoA/1C2GIT/HEAD/html/img/error.png -------------------------------------------------------------------------------- /html/img/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazarenkoA/1C2GIT/HEAD/html/img/success.png -------------------------------------------------------------------------------- /nssm-2.24/win32/nssm.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazarenkoA/1C2GIT/HEAD/nssm-2.24/win32/nssm.exe -------------------------------------------------------------------------------- /nssm-2.24/win64/nssm.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazarenkoA/1C2GIT/HEAD/nssm-2.24/win64/nssm.exe -------------------------------------------------------------------------------- /doc/img/browser_KnoJenu9An.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LazarenkoA/1C2GIT/HEAD/doc/img/browser_KnoJenu9An.jpg -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Example/MapUsers.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Insolent": "Insolent.AN ", 3 | "Ivanov": "Ivanov ", 4 | "hamstrings": "hamstrings.MG ", 5 | "Default": "Anonymous " 6 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /CreateService.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set SrvcName="1C2GIT_2" 3 | set BinPath="D:\1C2GIT\1C2GIT.exe" 4 | set Desctiption="Синхронизация 1С и Git" 5 | 6 | sc stop %SrvcName% 7 | sc delete %SrvcName% 8 | sc create %SrvcName% binPath= %BinPath% start= auto displayname= %SrvcName% 9 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /../../../../../../:\Users\lazarenko.an\IdeaProjects\1C2GIT\.idea/dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | run: 2 | tests: true 3 | skip-dirs: 4 | - bin 5 | - vendor 6 | - var 7 | - tmp 8 | 9 | output: 10 | format: colored-line-number 11 | print-issued-lines: true 12 | print-linter-name: true 13 | 14 | linters: 15 | enable-all: true 16 | disable: 17 | - gochecknoglobals 18 | - errcheck 19 | - gosimple 20 | - lll -------------------------------------------------------------------------------- /.idea/1C2GIT.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.14 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: 1.19 14 | id: go 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v1 18 | 19 | - name: Build 20 | run: | 21 | go build -v . 22 | env: 23 | GO111MODULE: on -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Logs 2 | /*.exe 3 | 4 | /debug 5 | /PTG_ACC_Extention 6 | /PTG_SM_Extention 7 | /PTG_HRM_Extention 8 | /Confs/*.yaml 9 | /PTG_HRM_Sheet_extension 10 | /PTG_Common 11 | /HRM_edu_extension 12 | /PTG_BPMN 13 | /PTG_HRM_WorkTime 14 | /.vscode/launch.json 15 | # Binaries for programs and plugins 16 | *.exe 17 | *.exe~ 18 | *.dll 19 | *.so 20 | *.dylib 21 | 22 | # Test binary, built with `go test -c` 23 | *.test 24 | 25 | # Output of the go coverage tool, specifically when used with LiteIDE 26 | *.out 27 | 28 | # Dependency directories (remove the comment below to include it) 29 | # vendor/ 30 | -------------------------------------------------------------------------------- /.idea/highlightedFiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/LazarenkoA/1C2GIT 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/LazarenkoA/LogrusRotate v1.0.1-0.20200420172000-d4b9e48cb178 7 | github.com/gorilla/websocket v1.4.2 8 | github.com/sirupsen/logrus v1.7.0 9 | go.uber.org/dig v1.10.0 10 | golang.org/x/text v0.13.0 11 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 12 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 13 | gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc 14 | gopkg.in/yaml.v2 v2.3.0 15 | ) 16 | 17 | require ( 18 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 19 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect 20 | github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect 21 | github.com/fsnotify/fsnotify v1.4.9 // indirect 22 | github.com/matryer/resync v0.0.0-20161211202428-d39c09a11215 // indirect 23 | golang.org/x/net v0.17.0 // indirect 24 | golang.org/x/sys v0.13.0 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /Example/Config.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Mongo": { 3 | "ConnectionString": "mongodb://127.0.0.1:27017" 4 | }, 5 | "Bin1C": "C:\\Program Files\\1cv8\\8.3.14.1694\\bin\\1cv8.exe", 6 | "RepositoryConf": [ 7 | { 8 | "TimerMinute": 30, 9 | "From": { 10 | "Rep": "tcp://server/HRM_Extention", 11 | "Extension": true, 12 | "Login": "Insolent", 13 | "Pass": "123" 14 | }, 15 | "To": { 16 | "RepDir": "C:\\Git\\Extensions\\HRM_Extention", 17 | "Branch": "Dev" 18 | } 19 | }, 20 | { 21 | "TimerMinute": 30, 22 | "From": { 23 | "Rep": "tcp://server/ACC_Extention", 24 | "Extension": true, 25 | "Login": "Insolent", 26 | "Pass": "123" 27 | }, 28 | "To": { 29 | "RepDir": "C:\\Git\\Extensions\\ACC_Extention", 30 | "Branch": "Dev" 31 | } 32 | }, 33 | { 34 | "TimerMinute": 30, 35 | "From": { 36 | "Rep": "tcp://server/Common", 37 | "Extension": true, 38 | "Login": "Insolent", 39 | "Pass": "123" 40 | }, 41 | "To": { 42 | "RepDir": "C:\\Git\\Extensions\\Common", 43 | "Branch": "Dev" 44 | } 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at laz-mail@mail.ru. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /Confs/settings.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | import ( 4 | "bufio" 5 | logrusRotate "github.com/LazarenkoA/LogrusRotate" 6 | "gopkg.in/yaml.v2" 7 | "io/ioutil" 8 | "os" 9 | "path" 10 | "time" 11 | 12 | "gopkg.in/xmlpath.v2" 13 | ) 14 | 15 | type Destination struct { 16 | RepDir string `yaml:"RepDir"` 17 | Branch string `yaml:"Branch"` 18 | } 19 | 20 | type RepositoryConf struct { 21 | TimerMinute int `yaml:"TimerMinute"` 22 | From *struct { 23 | Rep string `yaml:"Rep"` 24 | Login string `yaml:"Login"` 25 | Pass string `yaml:"Pass"` 26 | Extension bool `yaml:"Extension"` 27 | } `yaml:"From"` 28 | To *Destination `yaml:"To"` 29 | version string // для хранения версии конфигурации 30 | } 31 | 32 | type Setting struct { 33 | Bin1C string `yaml:"Bin1C"` 34 | RepositoryConf []*RepositoryConf `yaml:"RepositoryConf"` 35 | Mongo *struct { 36 | ConnectionString string `yaml:"ConnectionString"` 37 | } `yaml:"Mongo"` 38 | TFS *struct { 39 | URL string `yaml:"URL"` 40 | KEY string `yaml:"KEY"` 41 | } `yaml:"TFS"` 42 | } 43 | 44 | func ReadSettings(Filepath string, data interface{}) { 45 | if _, err := os.Stat(Filepath); os.IsNotExist(err) { 46 | logrusRotate.StandardLogger().WithField("файл", Filepath).Panic("Конфигурационный файл не найден") 47 | } 48 | 49 | file, err := ioutil.ReadFile(Filepath) 50 | if err != nil { 51 | logrusRotate.StandardLogger().WithField("файл", Filepath).WithError(err).Panic("Ошибка открытия файла") 52 | } 53 | 54 | err = yaml.Unmarshal(file, data) 55 | if err != nil { 56 | logrusRotate.StandardLogger().WithField("файл", Filepath).WithError(err).Panic("Ошибка чтения конфигурационного файла") 57 | } 58 | } 59 | 60 | func (r *RepositoryConf) GetRepPath() string { 61 | return r.From.Rep 62 | } 63 | 64 | func (r *RepositoryConf) GetLogin() string { 65 | return r.From.Login 66 | } 67 | 68 | func (r *RepositoryConf) GetPass() string { 69 | return r.From.Pass 70 | } 71 | 72 | func (r *RepositoryConf) IsExtension() bool { 73 | return r.From.Extension 74 | } 75 | 76 | func (r *RepositoryConf) GetDestination() *Destination { 77 | return r.To 78 | } 79 | 80 | func (r *RepositoryConf) GetTimerDuration() time.Duration { 81 | return time.Minute * time.Duration(r.TimerMinute) 82 | } 83 | 84 | // legacy 85 | func (this *RepositoryConf) SaveVersion() { 86 | logrusRotate.StandardLogger().WithField("Репозиторий", this.To.RepDir).WithField("Версия", this.version).Debug("Сохраняем версию расширения") 87 | 88 | ConfigurationFile := path.Join(this.To.RepDir, "Configuration.xml") 89 | if _, err := os.Stat(ConfigurationFile); os.IsNotExist(err) { 90 | logrusRotate.StandardLogger().WithField("Файл", ConfigurationFile).WithField("Репозиторий", this.GetRepPath()).Error("Конфигурационный файл (Configuration.xml) не найден") 91 | return 92 | } 93 | 94 | file, err := os.Open(ConfigurationFile) 95 | if err != nil { 96 | logrusRotate.StandardLogger().WithField("Файл", ConfigurationFile).WithField("Репозиторий", this.GetRepPath()).Errorf("Ошибка открытия: %q", err) 97 | return 98 | } 99 | defer file.Close() 100 | 101 | xmlroot, xmlerr := xmlpath.Parse(bufio.NewReader(file)) 102 | if xmlerr != nil { 103 | logrusRotate.StandardLogger().WithField("Файл", ConfigurationFile).Errorf("Ошибка чтения xml: %q", xmlerr.Error()) 104 | return 105 | } 106 | 107 | path_ := xmlpath.MustCompile("MetaDataObject/Configuration/Properties/Version/text()") 108 | if value, ok := path_.String(xmlroot); ok { 109 | this.version = value 110 | } else { 111 | // значит версии нет, установим начальную 112 | this.version = "1.0.0" 113 | logrusRotate.StandardLogger().WithField("Файл", ConfigurationFile).Debugf("В файле не было версии, установили %q", this.version) 114 | } 115 | 116 | } 117 | 118 | func (r *RepositoryConf) GetDir() string { 119 | return r.To.RepDir 120 | } 121 | func (r *RepositoryConf) GetBranch() string { 122 | return r.To.Branch 123 | } 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 1C to Git 3 | =========== 4 | #### Простая в настройки утилита для синхронизации хранилища 1С и GIT репозитория. 5 | 6 | [![Watch the video](doc/img/browser_KnoJenu9An.jpg)](https://www.youtube.com/watch?v=Dk2Vyh5PRcQ) 7 | 8 | Данное решение я постарался сделать максимально простым в настройки, по факту нужно два действия 9 | 10 | - Прописать настройки в конфиге 11 | - Запустить exe 12 | 13 | :warning: Соответственно у вас уже должен быть локальный каталог и Git репозиторий с настроенной синхронизацией в этот каталог! 14 | 15 | :warning: При первом запуске программа начнет получать историю начиная с первой версии в хранилище 1С, если вы хотите загружать историю начиная с определенной версии, тогда в каталоге с программой необходимо создать файл (без расширения) с именем репозитория в котором указать версию с которой необходимо начать загрузку. 16 | Например мы хотим синхронизировать репозиторий "tcp://192.168.0.1/acc" начиная с 34 версии, создаем в каталоге с exe файл `versions` c содержимым : 17 | ```yaml 18 | tcp://192.168.0.1/acc: 33 19 | ``` 20 | 21 | в файле может быть несколько строк, одна строка - одно хранилище 22 | ____ 23 | 24 | 25 | ### Начать использовать 26 | - Скачать актуальный [релиз](https://github.com/LazarenkoA/1C2GIT/releases) 27 | - Собрать в ручном режиме. Ставим [Go](https://blog.golang.org/), `git clone https://github.com/LazarenkoA/1C2GIT`, переходим в каталог, выполняем `go build -o "1C2GIT"` или `go build -o "1C2GIT.exe"`. (при использовании данного варианта версия будет 100% актуальная) 28 | 29 | Конфигурационных файлов два, оба располагаются в каталоге **Confs** 30 | 31 | - **Config.conf** - основной в который вносятся настройки синхронизации 32 | - **MapUsers.conf** - не обязательный, для того что бы задать соответствия пользователя хранилища и git. 33 | 34 | ### Структура Config.conf 35 | ```json 36 | { 37 | "Mongo": { 38 | "ConnectionString": "mongodb://127.0.0.1:27017" 39 | }, 40 | "Bin1C": "C:\\Program Files\\1cv8\\8.3.13.1513\\bin\\1cv8.exe", 41 | "RepositoryConf": [ 42 | { 43 | "TimerMinute": 30, 44 | "From": { 45 | "Rep": "tcp://....../.....", 46 | "Extension": true, 47 | "Login": "", 48 | "Pass": "" 49 | }, 50 | "To": { 51 | "RepDir": "", 52 | "Branch": "Dev" 53 | } 54 | } 55 | ] 56 | } 57 | ``` 58 | 59 | - **Mongo** - Содержит настройки подключения к Mongo DB. Не обязательный параметр, про плюсы использование mongo см. ниже. 60 | - **RepositoryConf** - это массив, т.е. таких элементов может быть несколько 61 | - **TimerMinute** - расписание в минутах через сколько будет производиться синхронизация 62 | - **From** - тут настраивается подключение к хранилищу 1С. 63 | - **Rep** - строка подключения к хранилищу, может быть как по tcp://, так и файловая шара. 64 | - **Extension** - флаг того является конфигурация расширением или нет. 65 | - **Login** - имя пользователя для подключения к хранилищу 66 | - **Pass** - пароль для подключения к хранилищу 67 | - **To** - тут настраивается выгрузка в git 68 | - **RepDir** - Директория в которой создан репозиторий для хранилища 69 | - **Branch** - Ветка в которую нужно коммитить 70 | 71 | ### Структура MapUsers.conf 72 | ```json 73 | { 74 | "Имя пользователя хранилища 1С": "Ivanov ", 75 | "Имя пользователя хранилища 1С2": "Petrov ", 76 | "Default": "Sidorov " 77 | } 78 | ``` 79 | Формат пользователя Git обязательно такой должен быть как в примере. Default - это тот пользователь, который будет взят, если не найдено явного соответствия. Если не найдено явное соответствие и нет указание Default, или вообще нет данного конфигурационного файла, коммиты в Git будут делаться без параметра --author, т.е. под тем пользователем под каким настроен Git. 80 | ##### Примеры конфигов можно посмотреть в каталоге Example 81 | 82 | 83 | ### Логирование 84 | Утилита поддерживает 4 уровня логирования 85 | - ошибка (2) 86 | - предупреждение (3) 87 | - информация (4) 88 | - дебаг (5) 89 | 90 | Уровень debug самый подробный. По умолчанию используется уровень "предупреждение". Задать уровень логирования можно при запуске параметром -LogLevel=5. 91 | Логи складываются в каталог **Logs** расположенный в директории с программой. 92 | 93 | ### Запуск как службы 94 | Для запуска синхронизации в виде службы можно воспользоваться приложением nssm. 95 | 96 | _nssm install 1C2GIT_ - Добавление службы 97 | 98 | _nssm set 1C2GIT Description "Выгрузка хранилища 1С в Git"_ - Установить описание для службы 99 | 100 | _nssm remove 1C2GIT_ - Удаление службы 101 | 102 | Подробности см. readme в каталоге nssm-2.24 103 | 104 | 105 | ### Web интерфейс 106 | http://localhost:2020 107 | 108 | Обновления лога в веб интервейсе происходи через веб сокет, т.о. перезагрузка страницы не требуется, что бы эта фишка работала нужно 109 | в html\index.html в строке 110 | `conn = new WebSocket("ws://127.0.0.1:2020/notifications");` 111 | установите имя своего хоста вместо 127.0.0.1 112 | 113 | 114 | В web интерфейс выводятся как ошибки, так и информация о том, что произошла очередная синхронизация 115 | ![screenshot](doc/img/687477.png) 116 | 117 | Для корректной работы web интерфейса необходимо в конфиге подключить Mongo DB. Без Mongo DB работать тоже будет, но в таком случае 118 | информация по коммитам накапливается в памяти приложения и следовательно после перезапуска все очистится, так же без Mongo DB не выводится диаграмма. 119 | 120 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/LazarenkoA/LogrusRotate v1.0.1-0.20200420172000-d4b9e48cb178 h1:PM3OtqhCe1RrN02/iQKZcvXS57QhlFJWJvmmRPb2z2k= 2 | github.com/LazarenkoA/LogrusRotate v1.0.1-0.20200420172000-d4b9e48cb178/go.mod h1:MeLoVkQYRN9RmnGzbN8YZGsp2t6EZs+o9tcIy0IX6GI= 3 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 4 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 5 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= 6 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 7 | github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= 8 | github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 13 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 14 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 15 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 16 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 17 | github.com/matryer/resync v0.0.0-20161211202428-d39c09a11215 h1:hDa3vAq/Zo5gjfJ46XMsGFbH+hTizpR4fUzQCk2nxgk= 18 | github.com/matryer/resync v0.0.0-20161211202428-d39c09a11215/go.mod h1:LH+NgPY9AJpDfqAFtzyer01N9MYNsAKUf3DC9DV1xIY= 19 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 20 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 21 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 22 | github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= 23 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 24 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 25 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 27 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 28 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 29 | go.uber.org/dig v1.10.0 h1:yLmDDj9/zuDjv3gz8GQGviXMs9TfysIUMUilCpgzUJY= 30 | go.uber.org/dig v1.10.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw= 31 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 32 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= 33 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 34 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 35 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 36 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 37 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 38 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 39 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 40 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 44 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 46 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 47 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 48 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 49 | golang.org/x/tools v0.0.0-20191030062658-86caa796c7ab/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 50 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 51 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 52 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 53 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 54 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= 57 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= 58 | gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc h1:LMEBgNcZUqXaP7evD1PZcL6EcDVa2QOFuI+cqM3+AJM= 59 | gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc/go.mod h1:N8UOSI6/c2yOpa/XDz3KVUiegocTziPiqNkeNTMiG1k= 60 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 61 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 62 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 63 | -------------------------------------------------------------------------------- /nssm-2.24/ChangeLog.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.23 2 | ------------------ 3 | * NSSM once again calls TerminateProcess() correctly. 4 | 5 | Changes since 2.22 6 | ------------------ 7 | * NSSM no longer clutters the event log with "The specified 8 | procedure could not be found" on legacy Windows releases. 9 | 10 | * Fixed failure to set a local username to run the service. 11 | 12 | Changes since 2.21 13 | ------------------ 14 | * Existing services can now be managed using the GUI 15 | or on the command line. 16 | 17 | * NSSM can now set the priority class and processor 18 | affinity of the managed application. 19 | 20 | * NSSM can now apply an unconditional delay before 21 | restarting the application. 22 | 23 | * NSSM can now optionally rotate existing files when 24 | redirecting I/O. 25 | 26 | * Unqualified path names are now relative to the 27 | application startup directory when redirecting I/O. 28 | 29 | * NSSM can now set the service display name, description, 30 | startup type and log on details. 31 | 32 | * All services now receive a standard console window, 33 | allowing them to read input correctly (if running in 34 | interactive mode). 35 | 36 | Changes since 2.20 37 | ------------------ 38 | * Services installed from the GUI no longer have incorrect 39 | AppParameters set in the registry. 40 | 41 | Changes since 2.19 42 | ------------------ 43 | * Services installed from the commandline without using the 44 | GUI no longer have incorrect AppStopMethod* registry 45 | entries set. 46 | 47 | Changes since 2.18 48 | ------------------ 49 | * Support AppEnvironmentExtra to append to the environment 50 | instead of replacing it. 51 | 52 | * The GUI is significantly less sucky. 53 | 54 | Changes since 2.17 55 | ------------------ 56 | * Timeouts for each shutdown method can be configured in 57 | the registry. 58 | 59 | * The GUI is slightly less sucky. 60 | 61 | Changes since 2.16 62 | ------------------ 63 | * NSSM can now redirect the service's I/O streams to any path 64 | capable of being opened by CreateFile(). 65 | 66 | * Allow building on Visual Studio Express. 67 | 68 | * Silently ignore INTERROGATE control. 69 | 70 | * Try to send Control-C events to console applications when 71 | shutting them down. 72 | 73 | Changes since 2.15 74 | ------------------ 75 | * Fixed case where NSSM could kill unrelated processes when 76 | shutting down. 77 | 78 | Changes since 2.14 79 | ------------------ 80 | * NSSM is now translated into Italian. 81 | 82 | * Fixed GUI not allowing paths longer than 256 characters. 83 | 84 | Changes since 2.13 85 | ------------------ 86 | * Fixed default GUI language being French not English. 87 | 88 | Changes since 2.12 89 | ------------------ 90 | * Fixed failure to run on Windows 2000. 91 | 92 | Changes since 2.11 93 | ------------------ 94 | * NSSM is now translated into French. 95 | 96 | * Really ensure systems recovery actions can happen. 97 | 98 | The change supposedly introduced in v2.4 to allow service recovery 99 | actions to be activated when the application exits gracefully with 100 | a non-zero error code didn't actually work. 101 | 102 | Changes since 2.10 103 | ------------------ 104 | * Support AppEnvironment for compatibility with srvany. 105 | 106 | Changes since 2.9 107 | ----------------- 108 | * Fixed failure to compile messages.mc in paths containing spaces. 109 | 110 | * Fixed edge case with CreateProcess(). 111 | 112 | Correctly handle the case where the application executable is under 113 | a path which contains space and an executable sharing the initial 114 | part of that path (up to a space) exists. 115 | 116 | Changes since 2.8 117 | ----------------- 118 | * Fixed failure to run on Windows versions prior to Vista. 119 | 120 | Changes since 2.7 121 | ----------------- 122 | * Read Application, AppDirectory and AppParameters before each restart so 123 | a change to any one doesn't require restarting NSSM itself. 124 | 125 | * Fixed messages not being sent to the event log correctly in some 126 | cases. 127 | 128 | * Try to handle (strictly incorrect) quotes in AppDirectory. 129 | 130 | Windows directories aren't allowed to contain quotes so CreateProcess() 131 | will fail if the AppDirectory is quoted. Note that it succeeds even if 132 | Application itself is quoted as the application plus parameters are 133 | interpreted as a command line. 134 | 135 | * Fixed failed to write full arguments to AppParameters when 136 | installing a service. 137 | 138 | * Throttle restarts. 139 | 140 | Back off from restarting the application immediately if it starts 141 | successfully but exits too soon. The default value of "too soon" is 142 | 1500 milliseconds. This can be configured by adding a DWORD value 143 | AppThrottle to the registry. 144 | 145 | Handle resume messages from the service console to restart the 146 | application immediately even if it is throttled. 147 | 148 | * Try to kill the process tree gracefully. 149 | 150 | Before calling TerminateProcess() on all processes assocatiated with 151 | the monitored application, enumerate all windows and threads and 152 | post appropriate messages to them. If the application bothers to 153 | listen for such messages it has a chance to shut itself down gracefully. 154 | 155 | Changes since 2.6 156 | ----------------- 157 | * Handle missing registry values. 158 | 159 | Warn if AppParameters is missing. Warn if AppDirectory is missing or 160 | unset and choose a fallback directory. 161 | First try to find the parent directory of the application. If that 162 | fails, eg because the application path is just "notepad" or something, 163 | start in the Windows directory. 164 | 165 | * Kill process tree when stopping service. 166 | 167 | Ensure that all child processes of the monitored application are 168 | killed when the service stops by recursing through all running 169 | processes and terminating those whose parent is the application 170 | or one of its descendents. 171 | 172 | Changes since 2.5 173 | ----------------- 174 | * Removed incorrect ExpandEnvironmentStrings() error. 175 | 176 | A log_event() call was inadvertently left in the code causing an error 177 | to be set to the eventlog saying that ExpandEnvironmentStrings() had 178 | failed when it had actually succeeded. 179 | 180 | Changes since 2.4 181 | ----------------- 182 | * Allow use of REG_EXPAND_SZ values in the registry. 183 | 184 | * Don't suicide on exit status 0 by default. 185 | 186 | Suiciding when the application exits 0 will cause recovery actions to be 187 | taken. Usually this is inappropriate. Only suicide if there is an 188 | explicit AppExit value for 0 in the registry. 189 | 190 | Technically such behaviour could be abused to do something like run a 191 | script after successful completion of a service but in most cases a 192 | suicide is undesirable when no actual failure occurred. 193 | 194 | * Don't hang if startup parameters couldn't be determined. 195 | Instead, signal that the service entered the STOPPED state. 196 | Set START_PENDING state prior to actual startup. 197 | 198 | Changes since 2.3 199 | ----------------- 200 | * Ensure systems recovery actions can happen. 201 | 202 | In Windows versions earlier than Vista the service manager would only 203 | consider a service failed (and hence eligible for recovery action) if 204 | the service exited without setting its state to SERVICE_STOPPED, even if 205 | it signalled an error exit code. 206 | In Vista and later the service manager can be configured to treat a 207 | graceful shutdown with error code as a failure but this is not the 208 | default behaviour. 209 | 210 | Try to configure the service manager to use the new behaviour when 211 | starting the service so users who set AppExit to Exit can use recovery 212 | actions as expected. 213 | 214 | Also recognise the new AppExit option Suicide for use on pre-Vista 215 | systems. When AppExit is Suicide don't stop the service but exit 216 | inelegantly, which should be seen as a failure. 217 | 218 | Changes since 2.2 219 | ----------------- 220 | * Send properly formatted messages to the event log. 221 | 222 | * Fixed truncation of very long path lengths in the registry. 223 | 224 | Changes since 2.1 225 | ----------------- 226 | * Decide how to handle application exit. 227 | 228 | When the service exits with exit code n look in 229 | HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppExit\, 230 | falling back to the unnamed value if no such code is listed. Parse the 231 | (string) value of this entry as follows: 232 | 233 | Restart: Start the application again (NSSM default). 234 | Ignore: Do nothing (srvany default). 235 | Exit: Stop the service. 236 | 237 | Changes since 2.0 238 | ----------------- 239 | * Added support for building a 64-bit executable. 240 | 241 | * Added project files for newer versions of Visual Studio. 242 | -------------------------------------------------------------------------------- /Git/git.go: -------------------------------------------------------------------------------- 1 | package git 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | logrusRotate "github.com/LazarenkoA/LogrusRotate" 7 | "github.com/sirupsen/logrus" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | type Git struct { 15 | repDir string 16 | data I1CCommit 17 | author string 18 | env map[string]string 19 | logger *logrus.Entry 20 | 21 | //mu *sync.Mutex 22 | //gitBin string // если стоит git, то в системной переменной path будет путь к git 23 | } 24 | 25 | type I1CCommit interface { 26 | GetComment() string 27 | GetAuthor() string 28 | GetDateTime() *time.Time 29 | } 30 | 31 | // New - конструктор 32 | func (g *Git) New(repDir string, data I1CCommit, mapUser map[string]string) *Git { // mu *sync.Mutex, 33 | g.repDir = repDir 34 | g.data = data 35 | g.logger = logrusRotate.StandardLogger().WithField("name", "GIT") 36 | g.logger.WithField("Каталог", g.repDir).Debug("Create object") 37 | //g.mu = mu 38 | 39 | g.logger.WithField("Пользователь из хранилища", g.data.GetAuthor()). 40 | WithField("mapUser", mapUser). 41 | Debug("Получаем соответствие пользователей") 42 | if g.author = mapUser[g.data.GetAuthor()]; g.author == "" { 43 | if g.author = mapUser["Default"]; g.author == "" { 44 | g.logger.Panic("В конфиге MapUsers.conf не определен Default пользователь") 45 | } 46 | } 47 | g.logger.WithField("Автор", g.author).Debug("Create object") 48 | 49 | g.env = make(map[string]string) // что бы в Destroy вернуть то что было 50 | g.env["GIT_AUTHOR_NAME"] = os.Getenv("GIT_AUTHOR_NAME") 51 | g.env["GIT_COMMITTER_NAME"] = os.Getenv("GIT_COMMITTER_NAME") 52 | g.env["GIT_AUTHOR_EMAIL"] = os.Getenv("GIT_AUTHOR_EMAIL") 53 | g.env["GIT_COMMITTER_EMAIL"] = os.Getenv("GIT_COMMITTER_EMAIL") 54 | 55 | parts := strings.SplitN(g.author, " ", 2) 56 | // говорят, что лучше указывать переменные окружения для коммитов 57 | os.Setenv("GIT_AUTHOR_NAME", strings.Trim(parts[0], " ")) 58 | os.Setenv("GIT_COMMITTER_NAME", strings.Trim(parts[0], " ")) 59 | os.Setenv("GIT_AUTHOR_EMAIL", strings.Trim(parts[1], " ")) 60 | os.Setenv("GIT_COMMITTER_EMAIL", strings.Trim(parts[1], " ")) 61 | 62 | g.logger.WithField("Environ", os.Environ()).Debug("Create object") 63 | 64 | return g 65 | } 66 | 67 | func (g *Git) Destroy() { 68 | g.logger.WithField("Каталог", g.repDir).Debug("Destroy") 69 | 70 | for k, v := range g.env { 71 | os.Setenv(k, v) 72 | } 73 | 74 | g.logger.WithField("Environ", os.Environ()).Debug("Восстанавливаем переменные окружения") 75 | } 76 | 77 | func (g *Git) Checkout(branch, repDir string) error { 78 | g.logger.WithField("Каталог", g.repDir).Debug("Checkout") 79 | 80 | // notLock нужен для того, что бы не заблокировать самого себя, например вызов такой 81 | // CommitAndPush - Pull - Checkout, в этом случаи не нужно лочить т.к. в CommitAndPush уже залочено 82 | // if !notLock { 83 | // g.mu.Lock() 84 | // defer g.mu.Unlock() 85 | // } 86 | 87 | if cb, err := g.getCurrentBranch(); err != nil { 88 | return err 89 | } else if cb == branch { 90 | // Если текущая ветка = ветки назначения, просто выходим 91 | return nil 92 | } 93 | 94 | g.logger.WithField("Каталог", repDir).WithField("branch", branch).Debug("checkout") 95 | 96 | cmd := exec.Command("git", "checkout", branch) 97 | if _, err := run(cmd, repDir); err != nil { 98 | return err // Странно, но почему-то гит информацию о том что изменилась ветка пишет в Stderr 99 | } else { 100 | return nil 101 | } 102 | } 103 | 104 | func (g *Git) getCurrentBranch() (result string, err error) { 105 | var branches []string 106 | if branches, err = g.getBranches(); err != nil { 107 | return "", err 108 | } 109 | for _, b := range branches { 110 | // только так получилось текущую ветку определить 111 | if strings.Index(b, "*") > -1 { 112 | return strings.Trim(b, " *"), nil 113 | } 114 | } 115 | 116 | return "", fmt.Errorf("Не удалось определить текущую ветку.\nДоступные ветки %v", branches) 117 | } 118 | 119 | func (g *Git) getBranches() (result []string, err error) { 120 | g.logger.WithField("Каталог", g.repDir).Debug(" getBranches") 121 | 122 | if _, err = os.Stat(g.repDir); os.IsNotExist(err) { 123 | err = fmt.Errorf("каталог %q Git репозитория не найден", g.repDir) 124 | g.logger.WithField("Каталог", g.repDir).Error(err) 125 | } 126 | result = []string{} 127 | 128 | cmd := exec.Command("git", "branch") 129 | if res, err := run(cmd, g.repDir); err != nil { 130 | return []string{}, err 131 | } else { 132 | for _, branch := range strings.Split(res, "\n") { 133 | if branch == "" { 134 | continue 135 | } 136 | result = append(result, strings.Trim(branch, " ")) 137 | } 138 | } 139 | return result, nil 140 | } 141 | 142 | func (g *Git) Pull(branch string) (err error) { 143 | // g.mu.Lock() 144 | // defer g.mu.Unlock() 145 | 146 | g.logger.WithField("Каталог", g.repDir).Debug("Pull") 147 | 148 | if _, err = os.Stat(g.repDir); os.IsNotExist(err) { 149 | err = fmt.Errorf("каталог %q Git репозитория не найден", g.repDir) 150 | g.logger.WithField("Каталог", g.repDir).Error(err) 151 | } 152 | 153 | if branch != "" { 154 | g.Checkout(branch, g.repDir) 155 | } 156 | 157 | cmd := exec.Command("git", "pull") 158 | if _, err := run(cmd, g.repDir); err != nil { 159 | return err 160 | } else { 161 | return nil 162 | } 163 | } 164 | 165 | func (g *Git) Push() (err error) { 166 | // g.mu.Lock() 167 | // defer g.mu.Unlock() 168 | g.logger.WithField("Каталог", g.repDir).Debug("Push") 169 | 170 | if _, err = os.Stat(g.repDir); os.IsNotExist(err) { 171 | err = fmt.Errorf("каталог %q Git репозитория не найден", g.repDir) 172 | g.logger.WithField("Каталог", g.repDir).Error(err) 173 | } 174 | 175 | cmd := exec.Command("git", "push") 176 | if _, err := run(cmd, g.repDir); err != nil { 177 | return err 178 | } else { 179 | return nil 180 | } 181 | } 182 | 183 | func (g *Git) Add() (err error) { 184 | // g.mu.Lock() 185 | // defer g.mu.Unlock() 186 | 187 | g.logger.WithField("Каталог", g.repDir).Debug("Add") 188 | 189 | if _, err = os.Stat(g.repDir); os.IsNotExist(err) { 190 | err = fmt.Errorf("каталог %q Git репозитория не найден", g.repDir) 191 | g.logger.WithField("Каталог", g.repDir).Error(err) 192 | } 193 | 194 | cmd := exec.Command("git", "add", ".") 195 | if _, err := run(cmd, g.repDir); err != nil { 196 | return err 197 | } else { 198 | return nil 199 | } 200 | } 201 | 202 | func (g *Git) ResetHard(branch string) (err error) { 203 | // g.mu.Lock() 204 | // defer g.mu.Unlock() 205 | 206 | g.logger.WithField("branch", branch).WithField("Каталог", g.repDir).Debug("ResetHard") 207 | 208 | if _, err = os.Stat(g.repDir); os.IsNotExist(err) { 209 | err = fmt.Errorf("каталог %q Git репозитория не найден", g.repDir) 210 | g.logger.WithField("Каталог", g.repDir).Error(err) 211 | } 212 | 213 | if branch != "" { 214 | g.Checkout(branch, g.repDir) 215 | } 216 | g.logger.WithField("branch", branch).WithField("Каталог", g.repDir).Debug("fetch") 217 | 218 | cmd := exec.Command("git", "fetch", "origin") 219 | run(cmd, g.repDir) 220 | 221 | cmd = exec.Command("git", "reset", "--hard", "origin/"+branch) 222 | if _, err := run(cmd, g.repDir); err != nil { 223 | return err 224 | } else { 225 | return nil 226 | } 227 | } 228 | 229 | func (g *Git) optimization() (err error) { 230 | 231 | g.logger.WithField("Каталог", g.repDir).Debug("optimization") 232 | 233 | if _, err = os.Stat(g.repDir); os.IsNotExist(err) { 234 | err = fmt.Errorf("каталог %q Git репозитория не найден", g.repDir) 235 | g.logger.WithField("Каталог", g.repDir).Error(err) 236 | } 237 | 238 | cmd := exec.Command("git", "gc", "--auto") 239 | if _, err := run(cmd, g.repDir); err != nil { 240 | return err 241 | } else { 242 | return nil 243 | } 244 | } 245 | 246 | func (g *Git) CommitAndPush(branch string) (err error) { 247 | // закоментировал, что бы не было дедлока т.е. в методах типа Add, Pull ... тоже лок накладывается 248 | // g.mu.Lock() 249 | // defer g.mu.Unlock() 250 | 251 | g.logger.WithField("Каталог", g.repDir).Debug("CommitAndPush") 252 | 253 | defer func() { g.logger.WithField("Каталог", g.repDir).Debug("end CommitAndPush") }() 254 | 255 | if _, err = os.Stat(g.repDir); os.IsNotExist(err) { 256 | err = fmt.Errorf("каталог %q Git репозитория не найден", g.repDir) 257 | g.logger.WithField("Каталог", g.repDir).Error(err) 258 | } 259 | 260 | err = g.Add() 261 | err = g.Pull(branch) 262 | if err != nil { 263 | return 264 | } 265 | 266 | // весь метот лочить не можем 267 | // g.mu.Lock() 268 | // func() { 269 | // defer g.mu.Unlock() 270 | 271 | date := g.data.GetDateTime().Format("2006.01.02 15:04:05") 272 | 273 | var param []string 274 | param = append(param, "commit") 275 | //param = append(param, "-a") 276 | param = append(param, "--allow-empty-message") 277 | param = append(param, fmt.Sprintf("--cleanup=verbatim")) 278 | param = append(param, fmt.Sprintf("--date=%v", date)) 279 | if g.author != "" { 280 | param = append(param, fmt.Sprintf("--author=%q", g.author)) 281 | } 282 | param = append(param, fmt.Sprintf("-m %v", g.data.GetComment())) 283 | param = append(param, strings.Replace(g.repDir, "\\", "/", -1)) 284 | 285 | cmdCommit := exec.Command("git", param...) 286 | if _, err = run(cmdCommit, g.repDir); err != nil { 287 | return err 288 | } 289 | // }() 290 | 291 | err = g.Push() 292 | err = g.optimization() 293 | 294 | return nil 295 | } 296 | 297 | func run(cmd *exec.Cmd, dir string) (string, error) { 298 | logrusRotate.StandardLogger().WithField("Исполняемый файл", cmd.Path). 299 | WithField("Параметры", cmd.Args). 300 | WithField("Каталог", dir). 301 | Debug("Выполняется команда git") 302 | 303 | cmd.Dir = dir 304 | cmd.Stdout = new(bytes.Buffer) 305 | cmd.Stderr = new(bytes.Buffer) 306 | 307 | err := cmd.Run() 308 | stderr := cmd.Stderr.(*bytes.Buffer).String() 309 | stdout := cmd.Stdout.(*bytes.Buffer).String() 310 | 311 | // Гит странный, вроде информационное сообщение как "nothing to commit, working tree clean" присылает в Stderr и статус выполнения 1, ну еба... 312 | // приходится костылить 313 | if err != nil && !strings.Contains(stdout, "nothing to commit") { 314 | errText := fmt.Sprintf("произошла ошибка запуска:\n err:%v \n Параметры: %v", err.Error(), cmd.Args) 315 | if stderr != "" { 316 | errText += fmt.Sprintf("StdErr:%v \n", stderr) 317 | } 318 | logrusRotate.StandardLogger().WithField("Исполняемый файл", cmd.Path). 319 | WithField("Stdout", stdout). 320 | Error(errText) 321 | return stdout, fmt.Errorf(errText) 322 | } else { 323 | return stdout, nil 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 1C2GIT 10 | 11 | 12 | 13 | 42 | 43 | 44 | 45 | 46 | 237 | 238 | 239 | 240 | 241 |
242 |
243 |

1C2GIT

244 |

Синхронизация хранилища 1С и Git

245 |
246 |
247 | 248 |
249 |
250 | {{range .Log}} 251 |
252 |
253 | {{if eq .type 1}} 254 | 255 | {{else}} 256 | 257 | {{end}} 258 |
259 |
{{.msg}} 260 | {{if ne .type 1}} 261 |
{{.comment}}
262 | {{end}} 263 |
264 |
265 | 266 | {{if ne .type 1}} 267 |
Автор: {{.autor}}
268 | {{end}} 269 |
{{.datetime}}
270 |
271 | 272 |
273 | {{end}} 274 |
275 | 276 | {{ if or (gt (len .ChartData) 0) (gt (len .ChartDataYear) 0)}} 277 |
278 | 279 | 280 |
281 | {{end}} 282 | 283 | 284 | 285 |
286 | {{range $key, $value := .ChartData}} 287 |
288 |
{{$key}}
289 |
{{$value}}
290 |
291 | {{end}} 292 |
293 | 294 |
295 | {{range $key, $value := .ChartDataYear}} 296 |
297 |
{{$key}}
298 |
{{join $value ","}}
299 |
300 | {{end}} 301 |
302 | 303 | 346 | 347 | 348 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/sha1" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | ConfigurationRepository "github.com/LazarenkoA/1C2GIT/Configuration" 10 | settings "github.com/LazarenkoA/1C2GIT/Confs" 11 | git "github.com/LazarenkoA/1C2GIT/Git" 12 | logrusRotate "github.com/LazarenkoA/LogrusRotate" 13 | "gopkg.in/mgo.v2/bson" 14 | "html/template" 15 | "log" 16 | "math" 17 | "net/http" 18 | "os" 19 | "path" 20 | "path/filepath" 21 | "strconv" 22 | "strings" 23 | "sync" 24 | "time" 25 | 26 | "github.com/gorilla/websocket" 27 | "github.com/sirupsen/logrus" 28 | di "go.uber.org/dig" 29 | "gopkg.in/alecthomas/kingpin.v2" 30 | "gopkg.in/mgo.v2" 31 | ) 32 | 33 | type RotateConf struct{} 34 | type msgtype byte 35 | 36 | const ( 37 | info msgtype = iota 38 | err 39 | 40 | ListenPort string = "2020" 41 | ) 42 | 43 | //////////////////////////////////////////////////////////// 44 | 45 | type Hook struct { 46 | } 47 | type event func(rep *ConfigurationRepository.Notify) 48 | 49 | func (h *Hook) Levels() []logrus.Level { 50 | return []logrus.Level{logrus.ErrorLevel, logrus.PanicLevel} 51 | } 52 | func (h *Hook) Fire(En *logrus.Entry) error { 53 | writeInfo(En.Message, "", "", time.Now(), err) 54 | return nil 55 | } 56 | 57 | const ( 58 | limit int = 17 59 | ) 60 | 61 | var ( 62 | LogLevel *int 63 | container *di.Container 64 | logchan chan map[string]interface{} 65 | mapUser map[string]string 66 | kp *kingpin.Application 67 | eventsBeforeCommit []event 68 | eventsAfterCommit []event 69 | logger *logrus.Entry 70 | ) 71 | 72 | func init() { 73 | // создаем контейнед DI 74 | container = di.New() 75 | 76 | logchan = make(chan map[string]interface{}, 10) 77 | mapUser = make(map[string]string) 78 | 79 | kp = kingpin.New("1C2GIT", "Приложение для синхронизации хранилища 1С и Git") 80 | LogLevel = kp.Flag("LogLevel", "Уровень логирования от 2 до 5\n"+ 81 | "\t2 - ошибка\n"+ 82 | "\t3 - предупреждение\n"+ 83 | "\t4 - информация\n"+ 84 | "\t5 - дебаг\n"). 85 | Short('l').Default("3").Int() 86 | 87 | //flag.BoolVar(&help, "help", false, "Помощь") 88 | } 89 | 90 | func main() { 91 | kp.Parse(os.Args[1:]) 92 | logrus.SetLevel(logrus.Level(2)) 93 | logrus.AddHook(new(Hook)) 94 | 95 | lw := new(logrusRotate.Rotate).Construct() 96 | defer lw.Start(*LogLevel, new(RotateConf))() 97 | logrus.SetFormatter(&logrus.JSONFormatter{}) 98 | 99 | logger = logrusRotate.StandardLogger().WithField("name", "main") 100 | 101 | httpInitialise() 102 | initDIProvide() 103 | 104 | // для тестирования 105 | //go func() { 106 | // timer := time.NewTicker(time.Second * 5) 107 | // for t := range timer.C { 108 | // writeInfo(fmt.Sprintf("test - %v", t.Second()), fake.FullName(), "", t, info) 109 | // } 110 | //}() 111 | 112 | var sLoc *settings.Setting 113 | if err := container.Invoke(func(s *settings.Setting) { 114 | sLoc = s 115 | }); err != nil { 116 | logger.WithError(err).Panic("не удалось прочитать настройки") 117 | } 118 | initEvents() 119 | 120 | rep := new(ConfigurationRepository.Repository).New(sLoc.Bin1C) 121 | wg := new(sync.WaitGroup) 122 | mu := new(sync.Mutex) 123 | 124 | for _, r := range sLoc.RepositoryConf { 125 | logger.WithField("repository", r.GetRepPath()).Info("запуск отслеживания изменений по репозиторию") 126 | 127 | wg.Add(1) 128 | go rep.Observe(r, wg, func(n *ConfigurationRepository.Notify, rep *ConfigurationRepository.Repository) error { 129 | return gitCommit(mu, rep, n) 130 | }) 131 | } 132 | 133 | fmt.Printf("Запуск ОК. Уровень логирования - %d\n", *LogLevel) 134 | wg.Wait() 135 | } 136 | 137 | func gitCommit(mu *sync.Mutex, rep *ConfigurationRepository.Repository, notify *ConfigurationRepository.Notify) error { 138 | mu.Lock() 139 | defer mu.Unlock() 140 | 141 | outDir := notify.RepInfo.GetDir() 142 | git_ := new(git.Git).New(outDir, notify, mapUser) 143 | defer git_.Destroy() 144 | defer func() { 145 | for _, e := range eventsAfterCommit { 146 | e(notify) 147 | } 148 | }() 149 | 150 | if err := git_.ResetHard(notify.RepInfo.GetBranch()); err != nil { 151 | logger.WithError(err).WithField("branch", notify.RepInfo.GetBranch()).Error("произошла ошибка при выполнении ResetHard") 152 | return err // если ветку не смогли переключить, логируемся и выходим, инчаче мы не в ту ветку закоммитим 153 | } 154 | 155 | // Запоминаем версию конфигурации. Сделано это потому что версия инерементируется в файлах, а не в хранилище 1С, что бы не перезатиралось. 156 | // TODO: подумать как обыграть это в настройках, а-ля файлы исключения, для xml файлов можно прикрутить xpath, что бы сохранять значение определенных узлов (как раз наш случай с версией) 157 | //r.SaveVersion() 158 | // Очищаем каталог перед выгрузкой, это нужно на случай если удаляется какой-то объект 159 | os.RemoveAll(outDir) 160 | 161 | // Как вариант можно параллельно грузить версии в темп каталоги, потом только переносить и пушить 162 | if err := rep.DownloadConfFiles(notify.RepInfo, notify.Version); err != nil { 163 | logger.WithField("Выгружаемая версия", notify.Version). 164 | WithField("Репозиторий", notify.RepInfo.GetRepPath()). 165 | Error("Ошибка выгрузки файлов из хранилища") 166 | return err 167 | } else { 168 | for _, e := range eventsBeforeCommit { 169 | e(notify) 170 | } 171 | 172 | rep.RestoreVersion(notify) // заисываем версию перед коммитом 173 | if err := git_.CommitAndPush(notify.RepInfo.GetBranch()); err != nil { 174 | logger.WithError(err).Error("Ошибка при выполнении push & commit") 175 | return err 176 | } 177 | 178 | logger.Debug("Синхронизация выполнена") 179 | writeInfo(fmt.Sprintf("Синхронизация %v выполнена", notify.RepInfo.GetRepPath()), notify.Author, notify.Comment, time.Now(), info) 180 | } 181 | 182 | return nil 183 | } 184 | 185 | func initDIProvide() { 186 | currentDir, _ := os.Getwd() 187 | settings.ReadSettings(path.Join(currentDir, "Confs", "MapUsers.yaml"), &mapUser) 188 | 189 | container.Provide(func() *settings.Setting { 190 | s := new(settings.Setting) 191 | settings.ReadSettings(path.Join(currentDir, "Confs", "Config.yaml"), s) 192 | return s 193 | }) 194 | container.Provide(func(s *settings.Setting) (*mgo.Database, error) { 195 | return connectToDB(s) 196 | }) 197 | tmp := &[]map[string]interface{}{} // в контейнере храним ссылку на слайс, что бы не приходилось обновлять каждый раз значение в контейнере 198 | container.Provide(func() *[]map[string]interface{} { 199 | return tmp 200 | }) 201 | } 202 | 203 | func httpInitialise() { 204 | go http.ListenAndServe(":"+ListenPort, nil) 205 | fmt.Printf("Слушаем порт http %v\n", ListenPort) 206 | 207 | currentDir, _ := os.Getwd() 208 | indexhtml := path.Join(currentDir, "html/index.html") 209 | if _, err := os.Stat(indexhtml); os.IsNotExist(err) { 210 | logger.WithField("Path", indexhtml).Error("Не найден index.html") 211 | return 212 | } 213 | 214 | tplFuncMap := make(template.FuncMap) 215 | tplFuncMap["join"] = func(data []int, separator string) string { 216 | tmp := make([]string, len(data)) 217 | for i, v := range data { 218 | tmp[i] = strconv.Itoa(v) 219 | } 220 | return strings.Join(tmp, separator) 221 | } 222 | tmpl, err := template.New(path.Base(indexhtml)).Funcs(tplFuncMap).ParseFiles(indexhtml) 223 | if err != nil { 224 | logger.WithError(err).Error("Ошибка парсинга шаблона") 225 | panic(err) 226 | } 227 | var upgrader = websocket.Upgrader{ 228 | ReadBufferSize: 1024, 229 | WriteBufferSize: 1024, 230 | CheckOrigin: func(r *http.Request) bool { 231 | return true 232 | }, 233 | } 234 | 235 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 236 | type tData struct { 237 | Log []map[string]interface{} 238 | ChartData map[string]int 239 | ChartDataYear map[string][]int 240 | } 241 | 242 | f := func(db *mgo.Database) error { 243 | var items []map[string]interface{} 244 | var monthitems []map[string]interface{} 245 | var yearitems []map[string]interface{} 246 | 247 | startMonth := time.Date(time.Now().Year(), time.Now().Month(), 1, 0, 0, 0, 0, time.Local) 248 | startYear := time.Date(time.Now().Year(), 1, 1, 0, 0, 0, 0, time.Local) 249 | 250 | // в монго фильтрация делается так {Time: {$gt: ISODate("2021-11-22")}} // для примера 251 | if err := getDataStartDate(db, startMonth, &monthitems); err != nil { 252 | return err 253 | } 254 | if err := getDataStartDate(db, startYear, &yearitems); err != nil { 255 | return err 256 | } 257 | 258 | logger.WithField("start time", startMonth).WithField("Получено данных", len(monthitems)). 259 | Debug("Запрашиваем данные из БД за текущий месяц") 260 | logger.WithField("start time", startYear).WithField("Получено данных", len(yearitems)). 261 | Debug("Запрашиваем данные из БД за год") 262 | 263 | chartData := map[string]int{} 264 | chartDataYear := map[string][]int{} 265 | for _, v := range monthitems { 266 | autor := strings.Trim(v["_id"].(map[string]interface{})["autor"].(string), " ") 267 | count := v["count"].(int) 268 | chartData[autor] += count // если в имени пользователя есть пробел или был ранее, то субд вернет 2 записи по одному пользователю 269 | } 270 | for _, v := range yearitems { 271 | autor := strings.Trim(v["_id"].(map[string]interface{})["autor"].(string), " ") 272 | month := v["_id"].(map[string]interface{})["month"].(int) 273 | count := v["count"].(int) 274 | 275 | if _, ok := chartDataYear[autor]; !ok { 276 | chartDataYear[autor] = make([]int, 12, 12) 277 | } 278 | 279 | chartDataYear[autor][month-1] += count 280 | } 281 | 282 | if err := db.C("items").Find(bson.M{"Time": bson.M{"$exists": true}}).Sort("-Time").Limit(limit).All(&items); err == nil { 283 | tmpl.Execute(w, tData{items, chartData, chartDataYear}) 284 | } else { 285 | logger.WithError(err).Error("Ошибка получения данных из БД") 286 | return err 287 | } 288 | 289 | return nil 290 | } 291 | 292 | if err := container.Invoke(f); err != nil { 293 | container.Invoke(func(logBufer *[]map[string]interface{}) { 294 | tmpl.Execute(w, tData{*logBufer, map[string]int{}, map[string][]int{}}) 295 | }) 296 | } 297 | }) 298 | 299 | // статический контент 300 | staticHandlerimg := http.StripPrefix( 301 | "/img/", 302 | http.FileServer(http.Dir("html/img")), 303 | ) 304 | staticHandlercss := http.StripPrefix( 305 | "/css/", 306 | http.FileServer(http.Dir("html/css")), 307 | ) 308 | staticHandlerscript := http.StripPrefix( 309 | "/script/", 310 | http.FileServer(http.Dir("html/script")), 311 | ) 312 | http.Handle("/img/", staticHandlerimg) 313 | http.Handle("/css/", staticHandlercss) 314 | http.Handle("/script/", staticHandlerscript) 315 | 316 | // Пояснение: 317 | // эта горутина нужна что бы читать из канала до того пока не загрузится http страничка (notifications) 318 | // потому как только тогда стартует чтение из канала, а если не читать из канала, у нас все выполнение застопорится 319 | // Сделано так, что при выполнении обработчика страницы notifications через контекст останавливается горутина 320 | ctx, cancel := context.WithCancel(context.Background()) 321 | go func() { 322 | exit: 323 | for range logchan { 324 | select { 325 | case <-ctx.Done(): 326 | break exit 327 | default: 328 | continue 329 | } 330 | } 331 | }() 332 | 333 | once := new(sync.Once) 334 | http.HandleFunc("/notifications", func(w http.ResponseWriter, r *http.Request) { 335 | ws, err := upgrader.Upgrade(w, r, nil) 336 | if err != nil { 337 | logger.WithError(err).Warning("Ошибка обновления веб сокета") 338 | return 339 | } 340 | 341 | go sendNewMsgNotifications(ws) 342 | 343 | // что б не запускалось при каждой перезагрузки страницы 344 | once.Do(func() { 345 | cancel() 346 | }) 347 | }) 348 | } 349 | 350 | func getDataStartDate(db *mgo.Database, startDate time.Time, result interface{}) error { 351 | group := []bson.M{ 352 | {"$match": bson.M{"Time": bson.M{"$gt": startDate, "$exists": true}}}, 353 | {"$group": bson.M{ 354 | "_id": bson.M{"month": bson.M{"$month": "$Time"}, "autor": "$autor"}, 355 | "count": bson.M{"$sum": 1}, 356 | }}, 357 | {"$sort": bson.M{"_id": 1}}, 358 | } 359 | return db.C("items").Pipe(group).All(result) 360 | } 361 | 362 | func writeInfo(str, autor, comment string, datetime time.Time, t msgtype) { 363 | log.Println(str) 364 | 365 | data := map[string]interface{}{ 366 | //"_id": bson.NewObjectId(), 367 | "msg": str, 368 | "datetime": datetime.Format("02.01.2006 (15:04)"), 369 | "comment": comment, 370 | "type": t, 371 | "autor": autor, 372 | "Time": datetime, 373 | } 374 | 375 | if err := container.Invoke(func(db *mgo.Database) { 376 | // Ошибки в монго не добавляем, нет смысла 377 | if t != err { 378 | db.C("items").Insert(data) 379 | } 380 | }); err != nil { 381 | container.Invoke(func(logBufer *[]map[string]interface{}) { 382 | // нужно на первое место поставить элемент, массив ограничиваем limit записями 383 | if len(*logBufer) > 0 { 384 | *logBufer = append((*logBufer)[:0], append([]map[string]interface{}{data}, (*logBufer)[0:]...)...) 385 | *logBufer = (*logBufer)[:int(math.Min(float64(len(*logBufer)), float64(limit)))] 386 | } else { 387 | *logBufer = append(*logBufer, data) 388 | } 389 | }) 390 | } 391 | 392 | logchan <- data 393 | } 394 | 395 | func sendNewMsgNotifications(client *websocket.Conn) { 396 | for Ldata := range logchan { 397 | w, err := client.NextWriter(websocket.TextMessage) 398 | if err != nil { 399 | logger.Warningf("Ошибка записи сокета: %v", err) 400 | break 401 | } 402 | 403 | data, _ := json.Marshal(Ldata) 404 | w.Write(data) 405 | w.Close() 406 | } 407 | } 408 | 409 | func GetHash(Str string) string { 410 | first := sha1.New() 411 | first.Write([]byte(Str)) 412 | 413 | return fmt.Sprintf("%x", first.Sum(nil)) 414 | } 415 | 416 | func connectToDB(s *settings.Setting) (*mgo.Database, error) { 417 | if s.Mongo == nil { 418 | return nil, errors.New("MongoDB not use") 419 | } 420 | logrusRotate.StandardLogger().Info("Подключаемся к MongoDB") 421 | if sess, err := mgo.Dial(s.Mongo.ConnectionString); err == nil { 422 | return sess.DB("1C2GIT"), nil 423 | } else { 424 | //logrusRotate.StandardLogger().WithError(err).Error("Ошибка подключения к MongoDB") 425 | fmt.Println("Ошибка подключения к MongoDB:", err) 426 | return nil, err 427 | } 428 | } 429 | 430 | func initEvents() { 431 | eventsBeforeCommit = []event{} 432 | eventsAfterCommit = []event{} 433 | } 434 | 435 | ///////////////// RotateConf //////////////////////////////////////////////////// 436 | func (w *RotateConf) LogDir() string { 437 | currentDir, _ := os.Getwd() 438 | return filepath.Join(currentDir, "Logs") 439 | } 440 | func (w *RotateConf) FormatDir() string { 441 | return "02.01.2006" 442 | } 443 | func (w *RotateConf) FormatFile() string { 444 | return "15" 445 | } 446 | func (w *RotateConf) TTLLogs() int { 447 | return 12 448 | } 449 | func (w *RotateConf) TimeRotate() int { 450 | return 1 451 | } 452 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | © 2020 GitHub, Inc. -------------------------------------------------------------------------------- /Configuration/conf.go: -------------------------------------------------------------------------------- 1 | package ConfigurationRepository 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | logrusRotate "github.com/LazarenkoA/LogrusRotate" 8 | "golang.org/x/text/encoding" 9 | "gopkg.in/yaml.v2" 10 | "io/ioutil" 11 | "os" 12 | "os/exec" 13 | "path" 14 | "path/filepath" 15 | "regexp" 16 | "strconv" 17 | "strings" 18 | "sync" 19 | "time" 20 | 21 | "github.com/sirupsen/logrus" 22 | ) 23 | 24 | type Repository struct { 25 | binPath string 26 | logger *logrus.Entry 27 | mu *sync.Mutex 28 | } 29 | 30 | type IRepositoryConf interface { 31 | GetRepPath() string 32 | GetLogin() string 33 | GetPass() string 34 | IsExtension() bool 35 | GetTimerDuration() time.Duration 36 | GetDir() string 37 | GetBranch() string 38 | } 39 | 40 | type Notify struct { 41 | RepInfo IRepositoryConf 42 | Comment string 43 | Version int 44 | Author string 45 | Date time.Time 46 | Err error 47 | } 48 | 49 | const ( 50 | temCfeName = "temp" 51 | versionFileName = "versions" 52 | ) 53 | 54 | func (this *Repository) New(binPath string) *Repository { 55 | this.binPath = binPath 56 | this.logger = logrusRotate.StandardLogger().WithField("name", "Repository") 57 | this.mu = new(sync.Mutex) 58 | 59 | return this 60 | } 61 | 62 | func (r *Notify) GetComment() string { 63 | return r.Comment 64 | } 65 | 66 | func (r *Notify) GetAuthor() string { 67 | return strings.Trim(r.Author, " ") 68 | } 69 | 70 | func (r *Notify) GetDateTime() *time.Time { 71 | return &r.Date 72 | } 73 | 74 | func (this *Repository) createTmpFile() string { 75 | //currentDir, _ := os.Getwd() 76 | fileLog, err := ioutil.TempFile("", "OutLog_") 77 | if err != nil { 78 | panic(fmt.Errorf("Ошибка получения временного файла:\n %v", err)) 79 | } 80 | 81 | fileLog.Close() // Закрываем иначе в него 1С не сможет записать 82 | return fileLog.Name() 83 | } 84 | 85 | // CreateTmpBD метод создает временную базу данных 86 | func (this *Repository) createTmpBD(tmpDBPath string, withExtension bool) (err error) { 87 | var Ext string 88 | 89 | if withExtension { 90 | currentDir, _ := os.Getwd() 91 | Ext = filepath.Join(currentDir, "tmp.cfe") 92 | 93 | if _, err := os.Stat(Ext); os.IsNotExist(err) { 94 | return fmt.Errorf("В каталоге с программой не найден файл расширения tmp.cfe") 95 | } 96 | } 97 | 98 | defer func() { 99 | if er := recover(); er != nil { 100 | err = fmt.Errorf("произошла ошибка при создании временной базы: %v", er) 101 | this.logger.Error(err) 102 | os.RemoveAll(tmpDBPath) 103 | } 104 | }() 105 | 106 | fileLog := this.createTmpFile() 107 | defer func() { 108 | os.Remove(fileLog) 109 | }() 110 | 111 | var param []string 112 | 113 | if withExtension { 114 | param = append(param, "DESIGNER") 115 | param = append(param, "/F", tmpDBPath) 116 | param = append(param, "/DisableStartupDialogs") 117 | param = append(param, "/DisableStartupMessages") 118 | param = append(param, "/LoadCfg", Ext) 119 | param = append(param, "-Extension", temCfeName) 120 | } else { 121 | param = append(param, "CREATEINFOBASE") 122 | param = append(param, fmt.Sprintf("File='%s'", tmpDBPath)) 123 | } 124 | param = append(param, fmt.Sprintf("/OUT %v", fileLog)) 125 | 126 | cmd := exec.Command(this.binPath, param...) 127 | if err := this.run(cmd, fileLog); err != nil { 128 | this.logger.WithError(err).Panic("Ошибка создания информационной базы.") 129 | } 130 | 131 | this.logger.Debug(fmt.Sprintf("Создана tempDB '%s'", tmpDBPath)) 132 | 133 | return nil 134 | } 135 | 136 | func (this *Repository) getReport(DataRep IRepositoryConf, version int) ([]*Notify, error) { 137 | var result []*Notify 138 | 139 | report := this.saveReport(DataRep, version) 140 | if report == "" { 141 | return result, fmt.Errorf("получен пустой отчет по хранилищу %v", DataRep.GetRepPath()) 142 | } 143 | 144 | // Двойные кавычки в комментарии мешают, по этому мы заменяем из на одинарные 145 | report = strings.Replace(report, "\"\"", "'", -1) 146 | 147 | var tmpArray [][]string 148 | reg := regexp.MustCompile(`[{]"#","([^"]+)["][}]`) 149 | matches := reg.FindAllStringSubmatch(report, -1) 150 | for _, s := range matches { 151 | if s[1] == "Версия:" { 152 | tmpArray = append(tmpArray, []string{}) 153 | } 154 | 155 | if len(tmpArray) > 0 { 156 | tmpArray[len(tmpArray)-1] = append(tmpArray[len(tmpArray)-1], s[1]) 157 | } 158 | } 159 | 160 | r := strings.NewReplacer("\r", "", "\n", " ") 161 | for _, array := range tmpArray { 162 | RepInfo := &Notify{RepInfo: DataRep} 163 | for id, s := range array { 164 | switch s { 165 | case "Версия:": 166 | if version, err := strconv.Atoi(array[id+1]); err == nil { 167 | RepInfo.Version = version 168 | } 169 | case "Пользователь:": 170 | RepInfo.Author = array[id+1] 171 | case "Комментарий:": 172 | // Комментария может не быть, по этому вот такой костыльчик 173 | if array[id+1] != "Изменены:" { 174 | RepInfo.Comment = r.Replace(array[id+1]) 175 | } 176 | case "Дата создания:": 177 | if t, err := time.Parse("02.01.2006", array[id+1]); err == nil { 178 | RepInfo.Date = t 179 | } 180 | case "Время создания:": 181 | if !RepInfo.Date.IsZero() { 182 | str := RepInfo.Date.Format("02.01.2006") + " " + array[id+1] 183 | if t, err := time.Parse("02.01.2006 15:04:05", str); err == nil { 184 | RepInfo.Date = t 185 | } 186 | } 187 | } 188 | } 189 | RepInfo.Comment = fmt.Sprintf("Хранилище: %v\n"+ 190 | "Версия: %v\n"+ 191 | "Коментарий: %q", DataRep.GetRepPath(), RepInfo.Version, RepInfo.Comment) 192 | result = append(result, RepInfo) 193 | } 194 | 195 | return result, nil 196 | } 197 | 198 | func (this *Repository) saveReport(DataRep IRepositoryConf, versionStart int) string { 199 | defer func() { 200 | if er := recover(); er != nil { 201 | this.logger.Error(fmt.Errorf("произошла ошибка при получении истории из хранилища: %v", er)) 202 | } 203 | }() 204 | 205 | this.logger.Debug("Сохраняем отчет конфигурации в файл") 206 | 207 | //currentDir, _ := os.Getwd() 208 | tmpDBPath, _ := ioutil.TempDir("", "1c_DB_") 209 | defer os.RemoveAll(tmpDBPath) 210 | 211 | if err := this.createTmpBD(tmpDBPath, DataRep.IsExtension()); err != nil { 212 | this.logger.WithError(err).Errorf("Произошла ошибка создания временной базы.") 213 | return "" 214 | } 215 | 216 | fileLog := this.createTmpFile() 217 | fileResult := this.createTmpFile() 218 | defer func() { 219 | os.Remove(fileLog) 220 | os.Remove(fileResult) 221 | }() 222 | 223 | var param []string 224 | param = append(param, "DESIGNER") 225 | param = append(param, "/DisableStartupDialogs") 226 | param = append(param, "/DisableStartupMessages") 227 | param = append(param, "/F", tmpDBPath) 228 | 229 | param = append(param, "/ConfigurationRepositoryF", DataRep.GetRepPath()) 230 | param = append(param, "/ConfigurationRepositoryN", DataRep.GetLogin()) 231 | param = append(param, "/ConfigurationRepositoryP", DataRep.GetPass()) 232 | param = append(param, "/ConfigurationRepositoryReport", fileResult) 233 | if versionStart > 0 { 234 | param = append(param, fmt.Sprintf("-NBegin %d", versionStart)) 235 | } 236 | if DataRep.IsExtension() { 237 | param = append(param, "-Extension", temCfeName) 238 | } 239 | param = append(param, "/OUT", fileLog) 240 | 241 | cmd := exec.Command(this.binPath, param...) 242 | 243 | if err := this.run(cmd, fileLog); err != nil { 244 | this.logger.Panic(err) 245 | } 246 | 247 | if b, err := this.readFile(fileResult, nil); err == nil { 248 | return string(b) 249 | } else { 250 | this.logger.Errorf("Произошла ошибка при чтерии отчета: %v", err) 251 | fmt.Printf("Произошла ошибка при чтерии отчета: %v", err) 252 | return "" 253 | } 254 | } 255 | 256 | func (this *Repository) run(cmd *exec.Cmd, fileLog string) (err error) { 257 | defer func() { 258 | if er := recover(); er != nil { 259 | err = fmt.Errorf("%v", er) 260 | this.logger.WithField("Параметры", cmd.Args).Errorf("Произошла ошибка при выполнении %q", cmd.Path) 261 | } 262 | }() 263 | 264 | this.logger.WithField("Исполняемый файл", cmd.Path). 265 | WithField("Параметры", cmd.Args). 266 | Debug("Выполняется команда пакетного запуска") 267 | 268 | timeout := time.Hour 269 | cmd.Stdout = new(bytes.Buffer) 270 | cmd.Stderr = new(bytes.Buffer) 271 | errch := make(chan error, 1) 272 | 273 | err = cmd.Start() 274 | if err != nil { 275 | return fmt.Errorf("Произошла ошибка запуска:\n\terr:%v\n\tПараметры: %v\n\t", err.Error(), cmd.Args) 276 | } 277 | 278 | // запускаем в горутине т.к. наблюдалось что при выполнении команд в пакетном режиме может происходить зависон, нам нужен таймаут 279 | go func() { 280 | errch <- cmd.Wait() 281 | }() 282 | 283 | select { 284 | case <-time.After(timeout): // timeout 285 | // завершаем процесс 286 | cmd.Process.Kill() 287 | return fmt.Errorf("Выполнение команды прервано по таймауту\n\tПараметры: %v\n\t", cmd.Args) 288 | case err := <-errch: 289 | if err != nil { 290 | stderr := cmd.Stderr.(*bytes.Buffer).String() 291 | errText := fmt.Sprintf("Произошла ошибка запуска:\n\terr:%v\n\tПараметры: %v\n\t", err.Error(), cmd.Args) 292 | if stderr != "" { 293 | errText += fmt.Sprintf("StdErr:%v\n", stderr) 294 | } 295 | 296 | if buf, err := this.readFile(fileLog, nil); err == nil { 297 | errText += string(buf) 298 | } 299 | 300 | return errors.New(errText) 301 | } else { 302 | return nil 303 | } 304 | } 305 | } 306 | 307 | func (this *Repository) getLastVersion(DataRep IRepositoryConf) (version int, err error) { 308 | this.logger.Debug(fmt.Sprintf("Читаем последнюю синхронизированную версию для %v\n", DataRep.GetRepPath())) 309 | 310 | this.mu.Lock() 311 | defer this.mu.Unlock() 312 | 313 | vInfo, err := this.readVersionsFile() 314 | if err != nil { 315 | this.logger.Error(fmt.Sprintf("Ошибка при чтении файла версий: %v\n", err)) 316 | return 0, err 317 | } 318 | 319 | return vInfo[DataRep.GetRepPath()], nil 320 | } 321 | 322 | func (this *Repository) saveLastVersion(DataRep IRepositoryConf, newVersion int) (err error) { 323 | this.logger.Debug(fmt.Sprintf("Записываем последнюю синхронизированную версию для %v (%v)\n", DataRep.GetRepPath(), newVersion)) 324 | 325 | this.mu.Lock() 326 | defer this.mu.Unlock() 327 | 328 | // при записи в общий файл может получится потеря данных, когда данные последовательно считываются, потом в своем потоке меняется своя версия расширения 329 | // при записи в файл версия другого расширения затирается 330 | // по этому, перед тем как записать, еще раз считываем с диска 331 | vInfo, err := this.readVersionsFile() 332 | if err != nil { 333 | this.logger.Error(fmt.Sprintf("Ошибка при чтении файла версий: %v\n", err)) 334 | return err 335 | } 336 | 337 | vInfo[DataRep.GetRepPath()] = newVersion 338 | 339 | currentDir, _ := os.Getwd() 340 | filePath := filepath.Join(currentDir, versionFileName) 341 | 342 | b, err := yaml.Marshal(vInfo) 343 | if err != nil { 344 | err = fmt.Errorf("ошибка сериализации: %v", err) 345 | this.logger.Error(fmt.Sprintf("Ошибка при записи файла версий: %v\n", err)) 346 | return 347 | } 348 | 349 | if err = ioutil.WriteFile(filePath, b, os.ModeAppend|os.ModePerm); err != nil { 350 | err = fmt.Errorf("ошибка записи файла %q", filePath) 351 | this.logger.Error(fmt.Sprintf("Ошибка при записи файла версий: %v\n", err)) 352 | return 353 | } 354 | 355 | return err 356 | } 357 | 358 | func (this *Repository) Observe(repInfo IRepositoryConf, wg *sync.WaitGroup, notify func(*Notify, *Repository) error) { 359 | defer wg.Done() 360 | 361 | l := this.logger.WithField("Репозиторий", repInfo.GetRepPath()) 362 | if repInfo.GetTimerDuration().Minutes() <= 0 { 363 | l.Error("для репазитория не определен параметр TimerMinute") 364 | return 365 | } 366 | 367 | timer := time.NewTicker(repInfo.GetTimerDuration()) 368 | defer timer.Stop() 369 | 370 | for { 371 | func() { 372 | version, err := this.getLastVersion(repInfo) 373 | if err != nil { 374 | l.WithError(err).Error("ошибка получения последней синхронизированной версиии") 375 | return 376 | } 377 | 378 | l.WithField("Начальная ревизия", version).Debug("Старт выгрузки") 379 | report, err := this.getReport(repInfo, version+1) 380 | if err != nil { 381 | l.WithError(err).Error("ошибка получения отчета по хранилищу") 382 | return 383 | } 384 | if len(report) == 0 { 385 | l.Debug("новых версий не найдено") 386 | return 387 | } 388 | 389 | for _, _report := range report { 390 | if err := notify(_report, this); err == nil { 391 | if e := this.saveLastVersion(repInfo, _report.Version); e != nil { 392 | l.WithError(e).Error("ошибка обновления последней синхронизированной версиии") 393 | } 394 | } 395 | } 396 | }() 397 | 398 | <-timer.C 399 | } 400 | } 401 | 402 | func (this *Repository) readFile(filePath string, Decoder *encoding.Decoder) ([]byte, error) { 403 | if fileB, err := ioutil.ReadFile(filePath); err == nil { 404 | // Разные кодировки = разные длины символов. 405 | if Decoder != nil { 406 | newBuf := make([]byte, len(fileB)*2) 407 | Decoder.Transform(newBuf, fileB, false) 408 | 409 | return newBuf, nil 410 | } else { 411 | return fileB, nil 412 | } 413 | } else { 414 | return []byte{}, fmt.Errorf("Ошибка открытия файла %q:\n %v", filePath, err) 415 | } 416 | } 417 | 418 | func (this *Repository) RestoreVersion(n *Notify) { 419 | l := this.logger.WithField("Репозиторий", n.RepInfo.GetDir()).WithField("Версия", n.Version) 420 | l.Debug("Восстанавливаем версию расширения") 421 | 422 | ConfigurationFile := path.Join(n.RepInfo.GetDir(), "Configuration.xml") 423 | if _, err := os.Stat(ConfigurationFile); os.IsNotExist(err) { 424 | l.WithField("Файл", ConfigurationFile).Error("конфигурационный файл не найден") 425 | return 426 | } 427 | 428 | // Меняем версию, без парсинга, поменять значение одного узла прям проблема, а повторять структуру xml в структуре ой как не хочется 429 | // Читаем файл 430 | file, err := os.Open(ConfigurationFile) 431 | if err != nil { 432 | l.WithField("Файл", ConfigurationFile).Errorf("Ошибка открытия файла: %q", err) 433 | return 434 | } 435 | 436 | stat, _ := file.Stat() 437 | buf := make([]byte, stat.Size()) 438 | if _, err = file.Read(buf); err != nil { 439 | l.WithField("Файл", ConfigurationFile).Errorf("Ошибка чтения файла: %q", err) 440 | return 441 | } 442 | file.Close() 443 | os.Remove(ConfigurationFile) 444 | 445 | xml := string(buf) 446 | reg := regexp.MustCompile(`(?i)(?:(.+?)|)`) 447 | //xml = reg.ReplaceAllString(xml, ""+this.version+"") 448 | xml = reg.ReplaceAllString(xml, ""+strconv.Itoa(n.Version)+"") 449 | 450 | // сохраняем файл 451 | file, err = os.OpenFile(ConfigurationFile, os.O_CREATE, os.ModeExclusive) 452 | if err != nil { 453 | l.WithError(err).WithField("Файл", ConfigurationFile).Error("Ошибка создания") 454 | return 455 | } 456 | defer file.Close() 457 | 458 | if _, err := file.WriteString(xml); err != nil { 459 | l.WithError(err).WithField("Файл", ConfigurationFile).Error("Ошибка записи") 460 | return 461 | } 462 | } 463 | 464 | func (this *Repository) readVersionsFile() (vInfo map[string]int, err error) { 465 | 466 | currentDir, _ := os.Getwd() 467 | filePath := filepath.Join(currentDir, versionFileName) 468 | if _, err := os.Stat(filePath); os.IsNotExist(err) { 469 | return nil, fmt.Errorf("файл версий не найден %q", filePath) 470 | } 471 | 472 | file, err := ioutil.ReadFile(filePath) 473 | if err != nil { 474 | return nil, fmt.Errorf("ошибка открытия файла версий %q", filePath) 475 | } 476 | 477 | vInfo = make(map[string]int, 0) 478 | err = yaml.Unmarshal(file, &vInfo) 479 | if err != nil { 480 | return nil, fmt.Errorf("ошибка чтения файла весрий %q", filePath) 481 | } 482 | 483 | return vInfo, nil 484 | } 485 | 486 | // Выгрузка конфигурации в файлы 487 | func (this *Repository) DownloadConfFiles(repInfo IRepositoryConf, version int) (err error) { 488 | defer func() { 489 | if er := recover(); er != nil { 490 | err = fmt.Errorf("произошла ошибка при сохранении конфигурации конфигурации в файлы: %v", er) 491 | } 492 | }() 493 | 494 | this.logger.Debug("Сохраняем конфигурацию в файлы") 495 | 496 | tmpDBPath, _ := ioutil.TempDir("", "1c_DB_") 497 | defer os.RemoveAll(tmpDBPath) 498 | 499 | if err = this.createTmpBD(tmpDBPath, repInfo.IsExtension()); err != nil { 500 | return err 501 | } 502 | 503 | // ПОДКЛЮЧАЕМ к ХРАНИЛИЩУ и ОБНОВЛЯЕМ ДО ОПРЕДЕЛЕННОЙ ВЕРСИИ 504 | this.configurationRepositoryBindCfg(repInfo, tmpDBPath, version) 505 | 506 | // СОХРАНЯЕМ В ФАЙЛЫ 507 | this.dumpConfigToFiles(repInfo, tmpDBPath) 508 | 509 | return nil 510 | } 511 | 512 | func (this *Repository) configurationRepositoryBindCfg(DataRep IRepositoryConf, fileDBPath string, version int) { 513 | fileLog := this.createTmpFile() 514 | defer os.Remove(fileLog) 515 | 516 | var param []string 517 | param = append(param, "DESIGNER") 518 | param = append(param, "/F", fileDBPath) 519 | param = append(param, "/DisableStartupDialogs") 520 | param = append(param, "/DisableStartupMessages") 521 | param = append(param, "/ConfigurationRepositoryF", DataRep.GetRepPath()) 522 | param = append(param, "/ConfigurationRepositoryN", DataRep.GetLogin()) 523 | param = append(param, "/ConfigurationRepositoryP", DataRep.GetPass()) 524 | param = append(param, "/ConfigurationRepositoryBindCfg") 525 | param = append(param, "-forceBindAlreadyBindedUser") 526 | param = append(param, "-forceReplaceCfg") 527 | if DataRep.IsExtension() { 528 | param = append(param, "-Extension", temCfeName) 529 | } 530 | 531 | param = append(param, "/ConfigurationRepositoryUpdateCfg") 532 | param = append(param, fmt.Sprintf("-v %d", version)) 533 | param = append(param, "-force") 534 | param = append(param, "-revised") 535 | if DataRep.IsExtension() { 536 | param = append(param, "-Extension", temCfeName) 537 | } 538 | 539 | param = append(param, fmt.Sprintf("/OUT %v", fileLog)) 540 | if err := this.run(exec.Command(this.binPath, param...), fileLog); err != nil { 541 | this.logger.Panic(err) 542 | } 543 | } 544 | 545 | func (this *Repository) dumpConfigToFiles(DataRep IRepositoryConf, fileDBPath string) { 546 | fileLog := this.createTmpFile() 547 | defer os.Remove(fileLog) 548 | 549 | var param []string 550 | param = append(param, "DESIGNER") 551 | param = append(param, "/F", fileDBPath) 552 | param = append(param, "/DisableStartupDialogs") 553 | param = append(param, "/DisableStartupMessages") 554 | param = append(param, fmt.Sprintf("/DumpConfigToFiles %v", DataRep.GetDir())) 555 | if DataRep.IsExtension() { 556 | param = append(param, "-Extension", temCfeName) 557 | } 558 | param = append(param, fmt.Sprintf("/OUT %v", fileLog)) 559 | if err := this.run(exec.Command(this.binPath, param...), fileLog); err != nil { 560 | this.logger.Panic(err) 561 | } 562 | } 563 | -------------------------------------------------------------------------------- /nssm-2.24/README.txt: -------------------------------------------------------------------------------- 1 | NSSM: The Non-Sucking Service Manager 2 | Version 2.24, 2014-08-31 3 | 4 | NSSM is a service helper program similar to srvany and cygrunsrv. It can 5 | start any application as an NT service and will restart the service if it 6 | fails for any reason. 7 | 8 | NSSM also has a graphical service installer and remover. 9 | 10 | Full documentation can be found online at 11 | 12 | http://nssm.cc/ 13 | 14 | Since version 2.0, the GUI can be bypassed by entering all appropriate 15 | options on the command line. 16 | 17 | Since version 2.1, NSSM can be compiled for x64 platforms. 18 | Thanks Benjamin Mayrargue. 19 | 20 | Since version 2.2, NSSM can be configured to take different actions 21 | based on the exit code of the managed application. 22 | 23 | Since version 2.3, NSSM logs to the Windows event log more elegantly. 24 | 25 | Since version 2.5, NSSM respects environment variables in its parameters. 26 | 27 | Since version 2.8, NSSM tries harder to shut down the managed application 28 | gracefully and throttles restart attempts if the application doesn't run 29 | for a minimum amount of time. 30 | 31 | Since version 2.11, NSSM respects srvany's AppEnvironment parameter. 32 | 33 | Since version 2.13, NSSM is translated into French. 34 | Thanks François-Régis Tardy. 35 | 36 | Since version 2.15, NSSM is translated into Italian. 37 | Thanks Riccardo Gusmeroli. 38 | 39 | Since version 2.17, NSSM can try to shut down console applications by 40 | simulating a Control-C keypress. If they have installed a handler routine 41 | they can clean up and shut down gracefully on receipt of the event. 42 | 43 | Since version 2.17, NSSM can redirect the managed application's I/O streams 44 | to an arbitrary path. 45 | 46 | Since version 2.18, NSSM can be configured to wait a user-specified amount 47 | of time for the application to exit when shutting down. 48 | 49 | Since version 2.19, many more service options can be configured with the 50 | GUI installer as well as via the registry. 51 | 52 | Since version 2.19, NSSM can add to the service's environment by setting 53 | AppEnvironmentExtra in place of or in addition to the srvany-compatible 54 | AppEnvironment. 55 | 56 | Since version 2.22, NSSM can set the managed application's process priority 57 | and CPU affinity. 58 | 59 | Since version 2.22, NSSM can apply an unconditional delay before restarting 60 | an application which has exited. 61 | 62 | Since version 2.22, NSSM can rotate existing output files when redirecting I/O. 63 | 64 | Since version 2.22, NSSM can set service display name, description, startup 65 | type, log on details and dependencies. 66 | 67 | Since version 2.22, NSSM can manage existing services. 68 | 69 | 70 | Usage 71 | ----- 72 | In the usage notes below, arguments to the program may be written in angle 73 | brackets and/or square brackets. means you must insert the 74 | appropriate string and [] means the string is optional. See the 75 | examples below... 76 | 77 | Note that everywhere appears you may substitute the 78 | service's display name. 79 | 80 | 81 | Installation using the GUI 82 | -------------------------- 83 | To install a service, run 84 | 85 | nssm install 86 | 87 | You will be prompted to enter the full path to the application you wish 88 | to run and any command line options to pass to that application. 89 | 90 | Use the system service manager (services.msc) to control advanced service 91 | properties such as startup method and desktop interaction. NSSM may 92 | support these options at a later time... 93 | 94 | 95 | Installation using the command line 96 | ----------------------------------- 97 | To install a service, run 98 | 99 | nssm install [] 100 | 101 | NSSM will then attempt to install a service which runs the named application 102 | with the given options (if you specified any). 103 | 104 | Don't forget to enclose paths in "quotes" if they contain spaces! 105 | 106 | If you want to include quotes in the options you will need to """quote""" the 107 | quotes. 108 | 109 | 110 | Managing the service 111 | -------------------- 112 | NSSM will launch the application listed in the registry when you send it a 113 | start signal and will terminate it when you send a stop signal. So far, so 114 | much like srvany. But NSSM is the Non-Sucking service manager and can take 115 | action if/when the application dies. 116 | 117 | With no configuration from you, NSSM will try to restart itself if it notices 118 | that the application died but you didn't send it a stop signal. NSSM will 119 | keep trying, pausing between each attempt, until the service is successfully 120 | started or you send it a stop signal. 121 | 122 | NSSM will pause an increasingly longer time between subsequent restart attempts 123 | if the service fails to start in a timely manner, up to a maximum of four 124 | minutes. This is so it does not consume an excessive amount of CPU time trying 125 | to start a failed application over and over again. If you identify the cause 126 | of the failure and don't want to wait you can use the Windows service console 127 | (where the service will be shown in Paused state) to send a continue signal to 128 | NSSM and it will retry within a few seconds. 129 | 130 | By default, NSSM defines "a timely manner" to be within 1500 milliseconds. 131 | You can change the threshold for the service by setting the number of 132 | milliseconds as a REG_DWORD value in the registry at 133 | HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppThrottle. 134 | 135 | Alternatively, NSSM can pause for a configurable amount of time before 136 | attempting to restart the application even if it successfully ran for the 137 | amount of time specified by AppThrottle. NSSM will consult the REG_DWORD value 138 | at HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppRestartDelay 139 | for the number of milliseconds to wait before attempting a restart. If 140 | AppRestartDelay is set and the application is determined to be subject to 141 | throttling, NSSM will pause the service for whichever is longer of the 142 | configured restart delay and the calculated throttle period. 143 | 144 | If AppRestartDelay is missing or invalid, only throttling will be applied. 145 | 146 | NSSM will look in the registry under 147 | HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppExit for 148 | string (REG_EXPAND_SZ) values corresponding to the exit code of the application. 149 | If the application exited with code 1, for instance, NSSM will look for a 150 | string value under AppExit called "1" or, if it does not find it, will 151 | fall back to the AppExit (Default) value. You can find out the exit code 152 | for the application by consulting the system event log. NSSM will log the 153 | exit code when the application exits. 154 | 155 | Based on the data found in the registry, NSSM will take one of three actions: 156 | 157 | If the value data is "Restart" NSSM will try to restart the application as 158 | described above. This is its default behaviour. 159 | 160 | If the value data is "Ignore" NSSM will not try to restart the application 161 | but will continue running itself. This emulates the (usually undesirable) 162 | behaviour of srvany. The Windows Services console would show the service 163 | as still running even though the application has exited. 164 | 165 | If the value data is "Exit" NSSM will exit gracefully. The Windows Services 166 | console would show the service as stopped. If you wish to provide 167 | finer-grained control over service recovery you should use this code and 168 | edit the failure action manually. Please note that Windows versions prior 169 | to Vista will not consider such an exit to be a failure. On older versions 170 | of Windows you should use "Suicide" instead. 171 | 172 | If the value data is "Suicide" NSSM will simulate a crash and exit without 173 | informing the service manager. This option should only be used for 174 | pre-Vista systems where you wish to apply a service recovery action. Note 175 | that if the monitored application exits with code 0, NSSM will only honour a 176 | request to suicide if you explicitly configure a registry key for exit code 0. 177 | If only the default action is set to Suicide NSSM will instead exit gracefully. 178 | 179 | 180 | Application priority 181 | -------------------- 182 | NSSM can set the priority class of the managed application. NSSM will look in 183 | the registry under HKLM\SYSTEM\CurrentControlSet\Services\\Parameters 184 | for the REG_DWORD entry AppPriority. Valid values correspond to arguments to 185 | SetPriorityClass(). If AppPriority() is missing or invalid the 186 | application will be launched with normal priority. 187 | 188 | 189 | Processor affinity 190 | ------------------ 191 | NSSM can set the CPU affinity of the managed application. NSSM will look in 192 | the registry under HKLM\SYSTEM\CurrentControlSet\Services\\Parameters 193 | for the REG_SZ entry AppAffinity. It should specify a comma-separated listed 194 | of zero-indexed processor IDs. A range of processors may optionally be 195 | specified with a dash. No other characters are allowed in the string. 196 | 197 | For example, to specify the first; second; third and fifth CPUs, an appropriate 198 | AppAffinity would be 0-2,4. 199 | 200 | If AppAffinity is missing or invalid, NSSM will not attempt to restrict the 201 | application to specific CPUs. 202 | 203 | Note that the 64-bit version of NSSM can configure a maximum of 64 CPUs in this 204 | way and that the 32-bit version can configure a maxium of 32 CPUs even when 205 | running on 64-bit Windows. 206 | 207 | 208 | Stopping the service 209 | -------------------- 210 | When stopping a service NSSM will attempt several different methods of killing 211 | the monitored application, each of which can be disabled if necessary. 212 | 213 | First NSSM will attempt to generate a Control-C event and send it to the 214 | application's console. Batch scripts or console applications may intercept 215 | the event and shut themselves down gracefully. GUI applications do not have 216 | consoles and will not respond to this method. 217 | 218 | Secondly NSSM will enumerate all windows created by the application and send 219 | them a WM_CLOSE message, requesting a graceful exit. 220 | 221 | Thirdly NSSM will enumerate all threads created by the application and send 222 | them a WM_QUIT message, requesting a graceful exit. Not all applications' 223 | threads have message queues; those which do not will not respond to this 224 | method. 225 | 226 | Finally NSSM will call TerminateProcess() to request that the operating 227 | system forcibly terminate the application. TerminateProcess() cannot be 228 | trapped or ignored, so in most circumstances the application will be killed. 229 | However, there is no guarantee that it will have a chance to perform any 230 | tidyup operations before it exits. 231 | 232 | Any or all of the methods above may be disabled. NSSM will look for the 233 | HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppStopMethodSkip 234 | registry value which should be of type REG_DWORD set to a bit field describing 235 | which methods should not be applied. 236 | 237 | If AppStopMethodSkip includes 1, Control-C events will not be generated. 238 | If AppStopMethodSkip includes 2, WM_CLOSE messages will not be posted. 239 | If AppStopMethodSkip includes 4, WM_QUIT messages will not be posted. 240 | If AppStopMethodSkip includes 8, TerminateProcess() will not be called. 241 | 242 | If, for example, you knew that an application did not respond to Control-C 243 | events and did not have a thread message queue, you could set AppStopMethodSkip 244 | to 5 and NSSM would not attempt to use those methods to stop the application. 245 | 246 | Take great care when including 8 in the value of AppStopMethodSkip. If NSSM 247 | does not call TerminateProcess() it is possible that the application will not 248 | exit when the service stops. 249 | 250 | By default NSSM will allow processes 1500ms to respond to each of the methods 251 | described above before proceeding to the next one. The timeout can be 252 | configured on a per-method basis by creating REG_DWORD entries in the 253 | registry under HKLM\SYSTEM\CurrentControlSet\Services\\Parameters. 254 | 255 | AppStopMethodConsole 256 | AppStopMethodWindow 257 | AppStopMethodThreads 258 | 259 | Each value should be set to the number of milliseconds to wait. Please note 260 | that the timeout applies to each process in the application's process tree, 261 | so the actual time to shutdown may be longer than the sum of all configured 262 | timeouts if the application spawns multiple subprocesses. 263 | 264 | 265 | Console window 266 | -------------- 267 | By default, NSSM will create a console window so that applications which 268 | are capable of reading user input can do so - subject to the service being 269 | allowed to interact with the desktop. 270 | 271 | Creation of the console can be suppressed by setting the integer (REG_DWORD) 272 | HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppNoConsole 273 | registry value to 1. 274 | 275 | 276 | I/O redirection 277 | --------------- 278 | NSSM can redirect the managed application's I/O to any path capable of being 279 | opened by CreateFile(). This enables, for example, capturing the log output 280 | of an application which would otherwise only write to the console or accepting 281 | input from a serial port. 282 | 283 | NSSM will look in the registry under 284 | HKLM\SYSTEM\CurrentControlSet\Services\\Parameters for the keys 285 | corresponding to arguments to CreateFile(). All are optional. If no path is 286 | given for a particular stream it will not be redirected. If a path is given 287 | but any of the other values are omitted they will be receive sensible defaults. 288 | 289 | AppStdin: Path to receive input. 290 | AppStdout: Path to receive output. 291 | AppStderr: Path to receive error output. 292 | 293 | Parameters for CreateFile() are providing with the "AppStdinShareMode", 294 | "AppStdinCreationDisposition" and "AppStdinFlagsAndAttributes" values (and 295 | analogously for stdout and stderr). 296 | 297 | In general, if you want the service to log its output, set AppStdout and 298 | AppStderr to the same path, eg C:\Users\Public\service.log, and it should 299 | work. Remember, however, that the path must be accessible to the user 300 | running the service. 301 | 302 | 303 | File rotation 304 | ------------- 305 | When using I/O redirection, NSSM can rotate existing output files prior to 306 | opening stdout and/or stderr. An existing file will be renamed with a 307 | suffix based on the file's last write time, to millisecond precision. For 308 | example, the file nssm.log might be rotated to nssm-20131221T113939.457.log. 309 | 310 | NSSM will look in the registry under 311 | HKLM\SYSTEM\CurrentControlSet\Services\\Parameters for REG_DWORD 312 | entries which control how rotation happens. 313 | 314 | If AppRotateFiles is missing or set to 0, rotation is disabled. Any non-zero 315 | value enables rotation. 316 | 317 | If AppRotateSeconds is non-zero, a file will not be rotated if its last write 318 | time is less than the given number of seconds in the past. 319 | 320 | If AppRotateBytes is non-zero, a file will not be rotated if it is smaller 321 | than the given number of bytes. 64-bit file sizes can be handled by setting 322 | a non-zero value of AppRotateBytesHigh. 323 | 324 | Rotation is independent of the CreateFile() parameters used to open the files. 325 | They will be rotated regardless of whether NSSM would otherwise have appended 326 | or replaced them. 327 | 328 | NSSM can also rotate files which hit the configured size threshold while the 329 | service is running. Additionally, you can trigger an on-demand rotation by 330 | running the command 331 | 332 | nssm rotate 333 | 334 | On-demand rotations will happen after the next line of data is read from 335 | the managed application, regardless of the value of AppRotateBytes. Be aware 336 | that if the application is not particularly verbose the rotation may not 337 | happen for some time. 338 | 339 | To enable online and on-demand rotation, set AppRotateOnline to a non-zero 340 | value. 341 | 342 | Note that online rotation requires NSSM to intercept the application's I/O 343 | and create the output files on its behalf. This is more complex and 344 | error-prone than simply redirecting the I/O streams before launching the 345 | application. Therefore online rotation is not enabled by default. 346 | 347 | 348 | Environment variables 349 | --------------------- 350 | NSSM can replace or append to the managed application's environment. Two 351 | multi-valued string (REG_MULTI_SZ) registry values are recognised under 352 | HKLM\SYSTEM\CurrentControlSet\Services\\Parameters. 353 | 354 | AppEnvironment defines a list of environment variables which will override 355 | the service's environment. AppEnvironmentExtra defines a list of 356 | environment variables which will be added to the service's environment. 357 | 358 | Each entry in the list should be of the form KEY=VALUE. It is possible to 359 | omit the VALUE but the = symbol is mandatory. 360 | 361 | Environment variables listed in both AppEnvironment and AppEnvironmentExtra 362 | are subject to normal expansion, so it is possible, for example, to update the 363 | system path by setting "PATH=C:\bin;%PATH%" in AppEnvironmentExtra. Variables 364 | are expanded in the order in which they appear, so if you want to include the 365 | value of one variable in another variable you should declare the dependency 366 | first. 367 | 368 | Because variables defined in AppEnvironment override the existing 369 | environment it is not possible to refer to any variables which were previously 370 | defined. 371 | 372 | For example, the following AppEnvironment block: 373 | 374 | PATH=C:\Windows\System32;C:\Windows 375 | PATH=C:\bin;%PATH% 376 | 377 | Would result in a PATH of "C:\bin;C:\Windows\System32;C:\Windows" as expected. 378 | 379 | Whereas the following AppEnvironment block: 380 | 381 | PATH=C:\bin;%PATH% 382 | 383 | Would result in a path containing only C:\bin and probably cause the 384 | application to fail to start. 385 | 386 | Most people will want to use AppEnvironmentExtra exclusively. srvany only 387 | supports AppEnvironment. 388 | 389 | 390 | Managing services using the GUI 391 | ------------------------------- 392 | NSSM can edit the settings of existing services with the same GUI that is 393 | used to install them. Run 394 | 395 | nssm edit 396 | 397 | to bring up the GUI. 398 | 399 | NSSM offers limited editing capabilities for services other than those which 400 | run NSSM itself. When NSSM is asked to edit a service which does not have 401 | the App* registry settings described above, the GUI will allow editing only 402 | system settings such as the service display name and description. 403 | 404 | 405 | Managing services using the command line 406 | ---------------------------------------- 407 | NSSM can retrieve or set individual service parameters from the command line. 408 | In general the syntax is as follows, though see below for exceptions. 409 | 410 | nssm get 411 | 412 | nssm set 413 | 414 | Parameters can also be reset to their default values. 415 | 416 | nssm reset 417 | 418 | The parameter names recognised by NSSM are the same as the registry entry 419 | names described above, eg AppDirectory. 420 | 421 | NSSM offers limited editing capabilities for Services other than those which 422 | run NSSM itself. The parameters recognised are as follows: 423 | 424 | Description: Service description. 425 | DisplayName: Service display name. 426 | ImagePath: Path to the service executable. 427 | ObjectName: User account which runs the service. 428 | Name: Service key name. 429 | Start: Service startup type. 430 | Type: Service type. 431 | 432 | These correspond to the registry values under the service's key 433 | HKLM\SYSTEM\CurrentControlSet\Services\. 434 | 435 | 436 | Note that NSSM will concatenate all arguments passed on the command line 437 | with spaces to form the value to set. Thus the following two invocations 438 | would have the same effect. 439 | 440 | nssm set Description "NSSM managed service" 441 | 442 | nssm set Description NSSM managed service 443 | 444 | 445 | Non-standard parameters 446 | ----------------------- 447 | The AppEnvironment and AppEnvironmentExtra parameters recognise an 448 | additional argument when querying the environment. The following syntax 449 | will print all extra environment variables configured for a service 450 | 451 | nssm get AppEnvironmentExtra 452 | 453 | whereas the syntax below will print only the value of the CLASSPATH 454 | variable if it is configured in the environment block, or the empty string 455 | if it is not configured. 456 | 457 | nssm get AppEnvironmentExtra CLASSPATH 458 | 459 | When setting an environment block, each variable should be specified as a 460 | KEY=VALUE pair in separate command line arguments. For example: 461 | 462 | nssm set AppEnvironment CLASSPATH=C:\Classes TEMP=C:\Temp 463 | 464 | 465 | The AppExit parameter requires an additional argument specifying the exit 466 | code to get or set. The default action can be specified with the string 467 | Default. 468 | 469 | For example, to get the default exit action for a service you should run 470 | 471 | nssm get AppExit Default 472 | 473 | To get the exit action when the application exits with exit code 2, run 474 | 475 | nssm get AppExit 2 476 | 477 | Note that if no explicit action is configured for a specified exit code, 478 | NSSM will print the default exit action. 479 | 480 | To set configure the service to stop when the application exits with an 481 | exit code of 2, run 482 | 483 | nssm set AppExit 2 Exit 484 | 485 | 486 | The AppPriority parameter is used to set the priority class of the 487 | managed application. Valid priorities are as follows: 488 | 489 | REALTIME_PRIORITY_CLASS 490 | HIGH_PRIORITY_CLASS 491 | ABOVE_NORMAL_PRIORITY_CLASS 492 | NORMAL_PRIORITY_CLASS 493 | BELOW_NORMAL_PRIORITY_CLASS 494 | IDLE_PRIORITY_CLASS 495 | 496 | 497 | The DependOnGroup and DependOnService parameters are used to query or set 498 | the dependencies for the service. When setting dependencies, each service 499 | or service group (preceded with the + symbol) should be specified in 500 | separate command line arguments. For example: 501 | 502 | nssm set DependOnService RpcSs LanmanWorkstation 503 | 504 | 505 | The Name parameter can only be queried, not set. It returns the service's 506 | registry key name. This may be useful to know if you take advantage of 507 | the fact that you can substitute the service's display name anywhere where 508 | the syntax calls for . 509 | 510 | 511 | The ObjectName parameter requires an additional argument only when setting 512 | a username. The additional argument is the password of the user. 513 | 514 | To retrieve the username, run 515 | 516 | nssm get ObjectName 517 | 518 | To set the username and password, run 519 | 520 | nssm set ObjectName 521 | 522 | Note that the rules of argument concatenation still apply. The following 523 | invocation is valid and will have the expected effect. 524 | 525 | nssm set ObjectName correct horse battery staple 526 | 527 | The following well-known usernames do not need a password. The password 528 | parameter can be omitted when using them: 529 | 530 | "LocalSystem" aka "System" aka "NT Authority\System" 531 | "LocalService" aka "Local Service" aka "NT Authority\Local Service" 532 | "NetworkService" aka "Network Service" aka "NT Authority\Network Service" 533 | 534 | 535 | The Start parameter is used to query or set the startup type of the service. 536 | Valid service startup types are as follows: 537 | 538 | SERVICE_AUTO_START: Automatic startup at boot. 539 | SERVICE_DELAYED_START: Delayed startup at boot. 540 | SERVICE_DEMAND_START: Manual service startup. 541 | SERVICE_DISABLED: The service is disabled. 542 | 543 | Note that SERVICE_DELAYED_START is not supported on versions of Windows prior 544 | to Vista. NSSM will set the service to automatic startup if delayed start is 545 | unavailable. 546 | 547 | 548 | The Type parameter is used to query or set the service type. NSSM recognises 549 | all currently documented service types but will only allow setting one of two 550 | types: 551 | 552 | SERVICE_WIN32_OWN_PROCESS: A standalone service. This is the default. 553 | SERVICE_INTERACTIVE_PROCESS: A service which can interact with the desktop. 554 | 555 | Note that a service may only be configured as interactive if it runs under 556 | the LocalSystem account. The safe way to configure an interactive service 557 | is in two stages as follows. 558 | 559 | nssm reset ObjectName 560 | nssm set Type SERVICE_INTERACTIVE_PROCESS 561 | 562 | 563 | Controlling services using the command line 564 | ------------------------------------------- 565 | NSSM offers rudimentary service control features. 566 | 567 | nssm start 568 | 569 | nssm restart 570 | 571 | nssm stop 572 | 573 | nssm status 574 | 575 | 576 | Removing services using the GUI 577 | ------------------------------- 578 | NSSM can also remove services. Run 579 | 580 | nssm remove 581 | 582 | to remove a service. You will prompted for confirmation before the service 583 | is removed. Try not to remove essential system services... 584 | 585 | 586 | Removing service using the command line 587 | --------------------------------------- 588 | To remove a service without confirmation from the GUI, run 589 | 590 | nssm remove confirm 591 | 592 | Try not to remove essential system services... 593 | 594 | 595 | Logging 596 | ------- 597 | NSSM logs to the Windows event log. It registers itself as an event log source 598 | and uses unique event IDs for each type of message it logs. New versions may 599 | add event types but existing event IDs will never be changed. 600 | 601 | Because of the way NSSM registers itself you should be aware that you may not 602 | be able to replace the NSSM binary if you have the event viewer open and that 603 | running multiple instances of NSSM from different locations may be confusing if 604 | they are not all the same version. 605 | 606 | 607 | Example usage 608 | ------------- 609 | To install an Unreal Tournament server: 610 | 611 | nssm install UT2004 c:\games\ut2004\system\ucc.exe server 612 | 613 | To run the server as the "games" user: 614 | 615 | nssm set UT2004 ObjectName games password 616 | 617 | To configure the server to log to a file: 618 | 619 | nssm set UT2004 AppStdout c:\games\ut2004\service.log 620 | 621 | To restrict the server to a single CPU: 622 | 623 | nssm set UT2004 AppAffinity 0 624 | 625 | To remove the server: 626 | 627 | nssm remove UT2004 confirm 628 | 629 | To find out the service name of a service with a display name: 630 | 631 | nssm get "Background Intelligent Transfer Service" Name 632 | 633 | 634 | Building NSSM from source 635 | ------------------------- 636 | NSSM is known to compile with Visual Studio 2008 and later. Older Visual 637 | Studio releases may or may not work if you install an appropriate SDK and 638 | edit the nssm.vcproj and nssm.sln files to set a lower version number. 639 | They are known not to work with default settings. 640 | 641 | NSSM will also compile with Visual Studio 2010 but the resulting executable 642 | will not run on versions of Windows older than XP SP2. If you require 643 | compatiblity with older Windows releases you should change the Platform 644 | Toolset to v90 in the General section of the project's Configuration 645 | Properties. 646 | 647 | 648 | Credits 649 | ------- 650 | Thanks to Bernard Loh for finding a bug with service recovery. 651 | Thanks to Benjamin Mayrargue (www.softlion.com) for adding 64-bit support. 652 | Thanks to Joel Reingold for spotting a command line truncation bug. 653 | Thanks to Arve Knudsen for spotting that child processes of the monitored 654 | application could be left running on service shutdown, and that a missing 655 | registry value for AppDirectory confused NSSM. 656 | Thanks to Peter Wagemans and Laszlo Keresztfalvi for suggesting throttling 657 | restarts. 658 | Thanks to Eugene Lifshitz for finding an edge case in CreateProcess() and for 659 | advising how to build messages.mc correctly in paths containing spaces. 660 | Thanks to Rob Sharp for pointing out that NSSM did not respect the 661 | AppEnvironment registry value used by srvany. 662 | Thanks to Szymon Nowak for help with Windows 2000 compatibility. 663 | Thanks to François-Régis Tardy and Gildas le Nadan for French translation. 664 | Thanks to Emilio Frini for spotting that French was inadvertently set as 665 | the default language when the user's display language was not translated. 666 | Thanks to Riccardo Gusmeroli and Marco Certelli for Italian translation. 667 | Thanks to Eric Cheldelin for the inspiration to generate a Control-C event 668 | on shutdown. 669 | Thanks to Brian Baxter for suggesting how to escape quotes from the command 670 | prompt. 671 | Thanks to Russ Holmann for suggesting that the shutdown timeout be configurable. 672 | Thanks to Paul Spause for spotting a bug with default registry entries. 673 | Thanks to BUGHUNTER for spotting more GUI bugs. 674 | Thanks to Doug Watson for suggesting file rotation. 675 | Thanks to Арслан Сайдуганов for suggesting setting process priority. 676 | Thanks to Robert Middleton for suggestion and draft implementation of process 677 | affinity support. 678 | Thanks to Andrew RedzMax for suggesting an unconditional restart delay. 679 | Thanks to Bryan Senseman for noticing that applications with redirected stdout 680 | and/or stderr which attempt to read from stdin would fail. 681 | Thanks to Czenda Czendov for help with Visual Studio 2013 and Server 2012R2. 682 | Thanks to Alessandro Gherardi for reporting and draft fix of the bug whereby 683 | the second restart of the application would have a corrupted environment. 684 | Thanks to Hadrien Kohl for suggesting to disable the console window's menu. 685 | Thanks to Allen Vailliencourt for noticing bugs with configuring the service to 686 | run under a local user account. 687 | Thanks to Sam Townsend for noticing a regression with TerminateProcess(). 688 | 689 | Licence 690 | ------- 691 | NSSM is public domain. You may unconditionally use it and/or its source code 692 | for any purpose you wish. 693 | --------------------------------------------------------------------------------