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 |
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 |
--------------------------------------------------------------------------------