Результаты поиска: Valid MB-500 Test Pattern 🤕 MB-500 Valid Test Tutorial ➕ Latest MB-500 Braindumps 🤥 Immediately open ⮆ www.pdfvce.com ⮄ and search for ▛ MB-500 ▟ to obtain a free download 🧑New MB-500 Exam Cram

Как автоматически скачивать сериалы и субтитры к ним

Довольно долгое время искал какие-то решения, позволяющие «подписаться» на торренты с интересными сериалами, но так как для меня важно наличие русский сабов (а их буржуи не раздают, как это ни странно), то часто реально оказывалось проще дождаться раздачи на наших сайтах, где все уже подбито. Но наконец, закинув в очередной раз невод в море, я выудил нечто похожее на золотую рыбку. Состоит рыбка из трех компонентов…

Торрент-клиент

В основе сервера будет transmission-daemon — идеальный для этого случая клиент. Примерный конфиг для него будет такой:

settings.json
 
  1. {
  2. "alt-speed-down": 50,
  3. "alt-speed-enabled": false,
  4. "alt-speed-time-begin": 540,
  5. "alt-speed-time-day": 127,
  6. "alt-speed-time-enabled": false,
  7. "alt-speed-time-end": 1020,
  8. "alt-speed-up": 50,
  9. "bind-address-ipv4": "0.0.0.0",
  10. "bind-address-ipv6": "::",
  11. "blocklist-enabled": true,
  12. "blocklist-url": "http://list.iblocklist.com/?list=bt_level1&fileformat=p2p&archiveformat=gz",
  13. "cache-size-mb": 4,
  14. "dht-enabled": true,
  15. "download-dir": "/share/torrents",
  16. "download-queue-enabled": true,
  17. "download-queue-size": 5,
  18. "encryption": 2,
  19. "idle-seeding-limit": 30,
  20. "idle-seeding-limit-enabled": false,
  21. "incomplete-dir": "/share/torrents/inc",
  22. "incomplete-dir-enabled": true,
  23. "lpd-enabled": true,
  24. "message-level": 1,
  25. "peer-congestion-algorithm": "",
  26. "peer-id-ttl-hours": 6,
  27. "peer-limit-global": 500,
  28. "peer-limit-per-torrent": 20,
  29. "peer-port": 59648,
  30. "peer-port-random-high": 65535,
  31. "peer-port-random-low": 49152,
  32. "peer-port-random-on-start": false,
  33. "peer-socket-tos": "default",
  34. "pex-enabled": true,
  35. "port-forwarding-enabled": true,
  36. "preallocation": 0,
  37. "prefetch-enabled": true,
  38. "queue-stalled-enabled": true,
  39. "queue-stalled-minutes": 30,
  40. "ratio-limit": 2,
  41. "ratio-limit-enabled": false,
  42. "rename-partial-files": true,
  43. "rpc-authentication-required": false,
  44. "rpc-bind-address": "0.0.0.0",
  45. "rpc-enabled": true,
  46. "rpc-host-whitelist": "",
  47. "rpc-host-whitelist-enabled": false,
  48. "rpc-password": "",
  49. "rpc-port": 9091,
  50. "rpc-url": "/transmission/",
  51. "rpc-username": "admin",
  52. "rpc-whitelist": "127.0.0.1",
  53. "rpc-whitelist-enabled": false,
  54. "scrape-paused-torrents-enabled": true,
  55. "script-torrent-done-enabled": false,
  56. "script-torrent-done-filename": "",
  57. "seed-queue-enabled": false,
  58. "seed-queue-size": 10,
  59. "speed-limit-down": 100,
  60. "speed-limit-down-enabled": false,
  61. "speed-limit-up": 100,
  62. "speed-limit-up-enabled": false,
  63. "start-added-torrents": true,
  64. "trash-original-torrent-files": false,
  65. "umask": 0,
  66. "upload-slots-per-torrent": 14,
  67. "utp-enabled": true,
  68. "watch-dir": "/share/torrents/watch",
  69. "watch-dir-enabled": true
  70. }

Этот конфиг подразумевает что у нас есть каталог ​/share/torrents, в который будут падать торренты, а также два подкаталога — inc и watch. Первый  для размещения файлов в процессе скачивания, второй для скачивания торрентов, вручную кинутых в этот каталог.

Вебморда: http://IP:9091/transmission/web/, логин admin без пароля

Граббилка торрентов

Шикарный проект torrentwatch-xa,  который мониторит RSS-фиды различных трекеров (есть набор дефолтных и возможность добавить свои), выцепляет названия, интересующие нас. и добавляет их на скачивание. Как правило сериалы выкладываются по сериям, так что свежие всегда будут появляться у нас как только так сразу.

Установка описана на гитхабе, так что сразу к настройкам. Прописываем настройки подключения к торрент-клиенту — он может быть как локальным, так и удаленным. Указываем корневую папку в которую будут скачиваться сериалы.

Указываем чтобы сериалы качались каждый в свою папку по названию сериала, и выставляем лимит раздачи (в данном случае 20 к 1, то есть гиг скачали — 20 раздали и остановились)

Вкладка Favorites отвечает за настройки мониторинга тех сериалов, которые мы захотим скачать. Использовать регулярные выражения для вычленения имени сериала, искать во всех фидах, качать только торренты с номерами сезона и эпизода в названии, скачивать только новые эпизоды (об этом ниже)

Теперь о том как выглядит наше избранное и как добавить сериал. Ну, примерно так

Разберем на примере доктора кто, имя торрента с его серией будет примерно таким: doctor.who.2005.s12e07.720p.hdtv.x264-mtb[eztv]

Имя — это просто имя, может быть произвольным; фильтр — как правило совпадает с именем указанным в имени торрента, но игнорирует точки; quality — качество, которое кодируется или как разрешение (720p), или как тип рипа (webrip/hdtv и т.д.), можно указывать или так или эдак; Last Downloaded — последний добавленный в скачивание эпизод (это поле обновляется автоматически, но его можно поменять и вручную, если часть эпизодов у нас уже есть и мы хотим качать только новое), при добавлении нового сериала это поле заполняется в формате SSxEE (SS — номер сезона, EE — номер эпизода, напр. 02×08)

Скачиватель субтитров

Как правило, для большинства сериалов субтитры рано или поздно находятся на opensubtitles.org, и было бы логично искать их там. Но хотелось бы делать это автоматически. И есть такой скрипт: https://github.com/emericg/OpenSubtitlesDownload

/opt/OpenSubtitlesDownload.py
 
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # OpenSubtitlesDownload.py / Version 4.1
  4. # This software is designed to help you find and download subtitles for your favorite videos!
  5. # You can browse the project's GitHub page:
  6. # https://github.com/emericg/OpenSubtitlesDownload
  7. # Learn much more about OpenSubtitlesDownload.py on its wiki:
  8. # https://github.com/emericg/OpenSubtitlesDownload/wiki
  9. # You can also browse the official website:
  10. # https://emeric.io/OpenSubtitlesDownload
  11. # Copyright (c) 2020 by Emeric GRANGE <emeric.grange@gmail.com>
  12. #
  13. # This program is free software: you can redistribute it and/or modify
  14. # it under the terms of the GNU General Public License as published by
  15. # the Free Software Foundation, either version 3 of the License, or
  16. # (at your option) any later version.
  17. #
  18. # This program is distributed in the hope that it will be useful,
  19. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21. # GNU General Public License for more details.
  22. #
  23. # You should have received a copy of the GNU General Public License
  24. # along with this program.  If not, see <https://www.gnu.org/licenses/>.
  25. # Contributors / special thanks:
  26. # Thiago Alvarenga Lechuga <thiagoalz@gmail.com> for his work on the 'Windows CLI' and the 'folder search'
  27. # jeroenvdw for his work on the 'subtitles automatic selection' and the 'search by filename'
  28. # Gui13 for his work on the arguments parsing
  29. # Tomáš Hnyk <tomashnyk@gmail.com> for his work on the 'multiple language' feature
  30. # Carlos Acedo <carlos@linux-labs.net> for his work on the original script
  31. import os
  32. import re
  33. import sys
  34. import time
  35. import gzip
  36. import struct
  37. import argparse
  38. import mimetypes
  39. import subprocess
  40. if sys.version_info >= (3, 0):
  41.     import shutil
  42.     import urllib.request
  43.     from xmlrpc.client import ServerProxy, Error
  44. else: # python2
  45.     import urllib
  46.     from xmlrpclib import ServerProxy, Error
  47. # ==== Opensubtitles.org server settings =======================================
  48. # XML-RPC server domain for opensubtitles.org:
  49. osd_server = ServerProxy('https://api.opensubtitles.org/xml-rpc')
  50. # You can use your opensubtitles.org account to avoid "in-subtitles" advertisment
  51. # and bypass download limits. Be careful about your password security, it will be
  52. # stored right here in plain text... You can also change opensubtitles.org language,
  53. # it will be used for error codes and stuff.
  54. osd_username = ''
  55. osd_password = ''
  56. osd_language = 'en'
  57. # ==== Language settings =======================================================
  58. # 1/ Change the search language by using any supported 3-letter (ISO 639-2) language codes:
  59. #    > Supported ISO codes: https://www.opensubtitles.org/addons/export_languages.php
  60. # 2/ Search for subtitles in several languages at once by using multiple codes separated by a comma:
  61. #    > Exemple: opt_languages = ['eng,fre']
  62. opt_languages = ['eng']
  63. # Write 2-letter language code (ex: _en) at the end of the subtitles file. 'on', 'off' or 'auto'.
  64. # If you are regularly searching for several language at once, you sould use 'on'.
  65. opt_language_suffix = 'auto'
  66. opt_language_separator = '_'
  67. # ==== Search settings =========================================================
  68. # Subtitles search mode. Can be overridden at run time with '-s' argument.
  69. # - hash (search by hash)
  70. # - filename (search by filename)
  71. # - hash_then_filename (search by hash, then filename if no results)
  72. # - hash_and_filename (search using both methods)
  73. opt_search_mode = 'hash_then_filename'
  74. # Search and download a subtitles even if a subtitles file already exists.
  75. opt_search_overwrite = 'on'
  76. # Subtitles selection mode. Can be overridden at run time with '-t' argument.
  77. # - manual (always let you choose the subtitles you want)
  78. # - default (in case of multiple results, let you choose the subtitles you want)
  79. # - auto (automatically select the best subtitles found)
  80. opt_selection_mode = 'default'
  81. # Customize subtitles download path. Can be overridden at run time with '-o' argument.
  82. # By default, subtitles are downloaded next to their video file.
  83. opt_output_path = ''
  84. # ==== GUI settings ============================================================
  85. # Select your GUI. Can be overridden at run time with '--gui=xxx' argument.
  86. # - auto (autodetection, fallback on CLI)
  87. # - gnome (GNOME/GTK based environments, using 'zenity' backend)
  88. # - kde (KDE/Qt based environments, using 'kdialog' backend)
  89. # - cli (Command Line Interface)
  90. opt_gui = 'auto'
  91. # Change the subtitles selection GUI size:
  92. opt_gui_width  = 720
  93. opt_gui_height = 320
  94. # Various GUI options. You can set them to 'on', 'off' or 'auto'.
  95. opt_selection_hi       = 'auto'
  96. opt_selection_language = 'auto'
  97. opt_selection_match    = 'auto'
  98. opt_selection_rating   = 'off'
  99. opt_selection_count    = 'off'
  100. # ==== Exit codes ==============================================================
  101. # Exit code returned by the software. You can use them to improve scripting behaviours.
  102. # 0: Success, and subtitles downloaded
  103. # 1: Success, but no subtitles found
  104. # 2: Failure
  105. # ==== Super Print =============================================================
  106. # priority: info, warning, error
  107. # title: only for zenity and kdialog messages
  108. # message: full text, with tags and breaks (tags will be cleaned up for CLI)
  109. def superPrint(priority, title, message):
  110.     """Print messages through terminal, zenity or kdialog"""
  111.     if opt_gui == 'gnome':
  112.         subprocess.call(['zenity', '--width=' + str(opt_gui_width), '--' + priority, '--title=' + title, '--text=' + message])
  113.     elif opt_gui == 'kde':
  114.         # Adapt to kdialog
  115.         message = message.replace("\n", "<br>")
  116.         message = message.replace('\\"', '"')
  117.         if priority == 'warning':
  118.             priority = 'sorry'
  119.         elif priority == 'info':
  120.             priority = 'msgbox'
  121.         subprocess.call(['kdialog', '--geometry=' + str(opt_gui_width) + 'x' + str(opt_gui_height), '--title=' + title, '--' + priority + '=' + message])
  122.     else:
  123.         # Clean up formating tags from the zenity messages
  124.         message = message.replace("\n\n", "\n")
  125.         message = message.replace("<i>", "")
  126.         message = message.replace("</i>", "")
  127.         message = message.replace("<b>", "")
  128.         message = message.replace("</b>", "")
  129.         message = message.replace('\\"', '"')
  130.         print(">> " + message)
  131. # ==== Check file path & type ==================================================
  132. def checkFileValidity(path):
  133.     """Check mimetype and/or file extension to detect valid video file"""
  134.     if os.path.isfile(path) is False:
  135.         return False
  136.     fileMimeType, encoding = mimetypes.guess_type(path)
  137.     if fileMimeType is None:
  138.         fileExtension = path.rsplit('.', 1)
  139.         if fileExtension[1] not in ['avi', 'mp4', 'mov', 'mkv', 'mk3d', 'webm', \
  140.                                     'ts', 'mts', 'm2ts', 'ps', 'vob', 'evo', 'mpeg', 'mpg', \
  141.                                     'm1v', 'm2p', 'm2v', 'm4v', 'movhd', 'movx', 'qt', \
  142.                                     'mxf', 'ogg', 'ogm', 'ogv', 'rm', 'rmvb', 'flv', 'swf', \
  143.                                     'asf', 'wm', 'wmv', 'wmx', 'divx', 'x264', 'xvid']:
  144.             #superPrint("error", "File type error!", "This file is not a video (unknown mimetype AND invalid file extension):\n<i>" + path + "</i>")
  145.             return False
  146.     else:
  147.         fileMimeType = fileMimeType.split('/', 1)
  148.         if fileMimeType[0] != 'video':
  149.             #superPrint("error", "File type error!", "This file is not a video (unknown mimetype):\n<i>" + path + "</i>")
  150.             return False
  151.     return True
  152. # ==== Check for existing subtitles file =======================================
  153. def checkSubtitlesExists(path):
  154.     """Check if a subtitles already exists for the current file"""
  155.     for ext in ['srt', 'sub', 'sbv', 'smi', 'ssa', 'ass', 'usf']:
  156.         subPath = path.rsplit('.', 1)[0] + '.' + ext
  157.         if os.path.isfile(subPath) is True:
  158.             superPrint("info", "Subtitles already downloaded!", "A subtitles file already exists for this file:\n<i>" + subPath + "</i>")
  159.             return True
  160.         # With language code? Only check the first language (and probably using the wrong language suffix format)
  161.         if opt_language_suffix in ('on', 'auto'):
  162.             if len(opt_languages) == 1:
  163.                 splitted_languages_list = opt_languages[0].split(',')
  164.             else:
  165.                 splitted_languages_list = opt_languages
  166.             subPath = path.rsplit('.', 1)[0] + opt_language_separator + splitted_languages_list[0] + '.' + ext
  167.             if os.path.isfile(subPath) is True:
  168.                 superPrint("info", "Subtitles already downloaded!", "A subtitles file already exists for this file:\n<i>" + subPath + "</i>")
  169.                 return True
  170.     return False
  171. # ==== Hashing algorithm =======================================================
  172. # Info: https://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes
  173. # This particular implementation is coming from SubDownloader: https://subdownloader.net
  174. def hashFile(path):
  175.     """Produce a hash for a video file: size + 64bit chksum of the first and
  176.     last 64k (even if they overlap because the file is smaller than 128k)"""
  177.     try:
  178.         longlongformat = 'Q' # unsigned long long little endian
  179.         bytesize = struct.calcsize(longlongformat)
  180.         fmt = "<%d%s" % (65536//bytesize, longlongformat)
  181.         f = open(path, "rb")
  182.         filesize = os.fstat(f.fileno()).st_size
  183.         filehash = filesize
  184.         if filesize < 65536 * 2:
  185.             superPrint("error", "File size error!", "File size error while generating hash for this file:\n<i>" + path + "</i>")
  186.             return "SizeError"
  187.         buf = f.read(65536)
  188.         longlongs = struct.unpack(fmt, buf)
  189.         filehash += sum(longlongs)
  190.         f.seek(-65536, os.SEEK_END) # size is always > 131072
  191.         buf = f.read(65536)
  192.         longlongs = struct.unpack(fmt, buf)
  193.         filehash += sum(longlongs)
  194.         filehash &= 0xFFFFFFFFFFFFFFFF
  195.         f.close()
  196.         returnedhash = "%016x" % filehash
  197.         return returnedhash
  198.     except IOError:
  199.         superPrint("error", "I/O error!", "Input/Output error while generating hash for this file:\n<i>" + path + "</i>")
  200.         return "IOError"
  201. # ==== GNOME selection window ==================================================
  202. def selectionGnome(subtitlesList):
  203.     """GNOME subtitles selection window using zenity"""
  204.     subtitlesSelected = ''
  205.     subtitlesItems = ''
  206.     subtitlesMatchedByHash = 0
  207.     subtitlesMatchedByName = 0
  208.     columnHi = ''
  209.     columnLn = ''
  210.     columnMatch = ''
  211.     columnRate = ''
  212.     columnCount = ''
  213.     # Generate selection window content
  214.     for item in subtitlesList['data']:
  215.         if item['MatchedBy'] == 'moviehash':
  216.             subtitlesMatchedByHash += 1
  217.         else:
  218.             subtitlesMatchedByName += 1
  219.         subtitlesItems += '"' + item['SubFileName'] + '" '
  220.         if opt_selection_hi == 'on':
  221.             columnHi = '--column="HI" '
  222.             if item['SubHearingImpaired'] == '1':
  223.                 subtitlesItems += '"✔" '
  224.             else:
  225.                 subtitlesItems += '"" '
  226.         if opt_selection_language == 'on':
  227.             columnLn = '--column="Language" '
  228.             subtitlesItems += '"' + item['LanguageName'] + '" '
  229.         if opt_selection_match == 'on':
  230.             columnMatch = '--column="MatchedBy" '
  231.             if item['MatchedBy'] == 'moviehash':
  232.                 subtitlesItems += '"HASH" '
  233.             else:
  234.                 subtitlesItems += '"" '
  235.         if opt_selection_rating == 'on':
  236.             columnRate = '--column="Rating" '
  237.             subtitlesItems += '"' + item['SubRating'] + '" '
  238.         if opt_selection_count == 'on':
  239.             columnCount = '--column="Downloads" '
  240.             subtitlesItems += '"' + item['SubDownloadsCnt'] + '" '
  241.     if subtitlesMatchedByName == 0:
  242.         tilestr = ' --title="Subtitles for: ' + videoTitle + '"'
  243.         textstr = ' --text="<b>Video title:</b> ' + videoTitle + '\n<b>File name:</b> ' + videoFileName + '"'
  244.     elif subtitlesMatchedByHash == 0:
  245.         tilestr = ' --title="Subtitles for: ' + videoFileName + '"'
  246.         textstr = ' --text="Search results using file name, NOT video detection. <b>May be unreliable...</b>\n<b>File name:</b> ' + videoFileName + '" '
  247.     else: # a mix of the two
  248.         tilestr = ' --title="Subtitles for: ' + videoTitle + '"'
  249.         textstr = ' --text="Search results using file name AND video detection.\n<b>Video title:</b> ' + videoTitle + '\n<b>File name:</b> ' + videoFileName + '"'
  250.     # Spawn zenity "list" dialog
  251.     process_subtitlesSelection = subprocess.Popen('zenity --width=' + str(opt_gui_width) + ' --height=' + str(opt_gui_height) + ' --list' + tilestr + textstr \
  252.         + ' --column="Available subtitles" ' + columnHi + columnLn + columnMatch + columnRate + columnCount + subtitlesItems, shell=True, stdout=subprocess.PIPE)
  253.     # Get back the result
  254.     result_subtitlesSelection = process_subtitlesSelection.communicate()
  255.     # The results contain a subtitles?
  256.     if result_subtitlesSelection[0]:
  257.         if sys.version_info >= (3, 0):
  258.             subtitlesSelected = str(result_subtitlesSelection[0], 'utf-8').strip("\n")
  259.         else: # python2
  260.             subtitlesSelected = str(result_subtitlesSelection[0]).strip("\n")
  261.         # Hack against recent zenity version?
  262.         if len(subtitlesSelected.split("|")) > 1:
  263.             if subtitlesSelected.split("|")[0] == subtitlesSelected.split("|")[1]:
  264.                 subtitlesSelected = subtitlesSelected.split("|")[0]
  265.     else:
  266.         if process_subtitlesSelection.returncode == 0:
  267.             subtitlesSelected = subtitlesList['data'][0]['SubFileName']
  268.     # Return the result
  269.     return subtitlesSelected
  270. # ==== KDE selection window ====================================================
  271. def selectionKde(subtitlesList):
  272.     """KDE subtitles selection window using kdialog"""
  273.     subtitlesSelected = ''
  274.     subtitlesItems = ''
  275.     subtitlesMatchedByHash = 0
  276.     subtitlesMatchedByName = 0
  277.     # Generate selection window content
  278.     # TODO doesn't support additional columns
  279.     index = 0
  280.     for item in subtitlesList['data']:
  281.         if item['MatchedBy'] == 'moviehash':
  282.             subtitlesMatchedByHash += 1
  283.         else:
  284.             subtitlesMatchedByName += 1
  285.         # key + subtitles name
  286.         subtitlesItems += str(index) + ' "' + item['SubFileName'] + '" '
  287.         index += 1
  288.     if subtitlesMatchedByName == 0:
  289.         tilestr = ' --title="Subtitles for ' + videoTitle + '"'
  290.         menustr = ' --menu="<b>Video title:</b> ' + videoTitle + '<br><b>File name:</b> ' + videoFileName + '" '
  291.     elif subtitlesMatchedByHash == 0:
  292.         tilestr = ' --title="Subtitles for ' + videoFileName + '"'
  293.         menustr = ' --menu="Search results using file name, NOT video detection. <b>May be unreliable...</b><br><b>File name:</b> ' + videoFileName + '" '
  294.     else: # a mix of the two
  295.         tilestr = ' --title="Subtitles for ' + videoTitle + '" '
  296.         menustr = ' --menu="Search results using file name AND video detection.<br><b>Video title:</b> ' + videoTitle + '<br><b>File name:</b> ' + videoFileName + '" '
  297.     # Spawn kdialog "radiolist"
  298.     process_subtitlesSelection = subprocess.Popen('kdialog --geometry=' + str(opt_gui_width) + 'x' + str(opt_gui_height) + tilestr + menustr + subtitlesItems, shell=True, stdout=subprocess.PIPE)
  299.     # Get back the result
  300.     result_subtitlesSelection = process_subtitlesSelection.communicate()
  301.     # The results contain the key matching a subtitles?
  302.     if result_subtitlesSelection[0]:
  303.         if sys.version_info >= (3, 0):
  304.             keySelected = int(str(result_subtitlesSelection[0], 'utf-8').strip("\n"))
  305.         else: # python2
  306.             keySelected = int(str(result_subtitlesSelection[0]).strip("\n"))
  307.         subtitlesSelected = subtitlesList['data'][keySelected]['SubFileName']
  308.     # Return the result
  309.     return subtitlesSelected
  310. # ==== CLI selection mode ======================================================
  311. def selectionCLI(subtitlesList):
  312.     """Command Line Interface, subtitles selection inside your current terminal"""
  313.     subtitlesIndex = 0
  314.     subtitlesItem = ''
  315.     # Print video infos
  316.     print("\n>> Title: " + videoTitle)
  317.     print(">> Filename: " + videoFileName)
  318.     # Print subtitles list on the terminal
  319.     print(">> Available subtitles:")
  320.     for item in subtitlesList['data']:
  321.         subtitlesIndex += 1
  322.         subtitlesItem = '"' + item['SubFileName'] + '" '
  323.         if opt_selection_hi == 'on' and item['SubHearingImpaired'] == '1':
  324.             subtitlesItem += '> "HI" '
  325.         if opt_selection_language == 'on':
  326.             subtitlesItem += '> "Language: ' + item['LanguageName'] + '" '
  327.         if opt_selection_match == 'on':
  328.             subtitlesItem += '> "MatchedBy: ' + item['MatchedBy'] + '" '
  329.         if opt_selection_rating == 'on':
  330.             subtitlesItem += '> "SubRating: ' + item['SubRating'] + '" '
  331.         if opt_selection_count == 'on':
  332.             subtitlesItem += '> "SubDownloadsCnt: ' + item['SubDownloadsCnt'] + '" '
  333.         if item['MatchedBy'] == 'moviehash':
  334.             print("\033[92m[" + str(subtitlesIndex) + "]\033[0m " + subtitlesItem)
  335.         else:
  336.             print("\033[93m[" + str(subtitlesIndex) + "]\033[0m " + subtitlesItem)
  337.     # Ask user selection
  338.     print("\033[91m[0]\033[0m Cancel search")
  339.     sub_selection = -1
  340.     while(sub_selection < 0 or sub_selection > subtitlesIndex):
  341.         try:
  342.             if sys.version_info >= (3, 0):
  343.                 sub_selection = int(input(">> Enter your choice (0-" + str(subtitlesIndex) + "): "))
  344.             else: # python 2
  345.                 sub_selection = int(raw_input(">> Enter your choice (0-" + str(subtitlesIndex) + "): "))
  346.         except:
  347.             sub_selection = -1
  348.     # Return the result
  349.     if sub_selection == 0:
  350.         print("Cancelling search...")
  351.         return ""
  352.     return subtitlesList['data'][sub_selection-1]['SubFileName']
  353. # ==== Automatic selection mode ================================================
  354. def selectionAuto(subtitlesList):
  355.     """Automatic subtitles selection using filename match"""
  356.     if len(opt_languages) == 1:
  357.         splitted_languages_list = list(reversed(opt_languages[0].split(',')))
  358.     else:
  359.         splitted_languages_list = opt_languages
  360.     videoFileParts = videoFileName.replace('-', '.').replace(' ', '.').replace('_', '.').lower().split('.')
  361.     maxScore = -1
  362.     for subtitle in subtitlesList['data']:
  363.         score = 0
  364.         # points to respect languages priority
  365.         score += splitted_languages_list.index(subtitle['SubLanguageID']) * 100
  366.         # extra point if the sub is found by hash
  367.         if subtitle['MatchedBy'] == 'moviehash':
  368.             score += 1
  369.         # points for filename mach
  370.         subFileParts = subtitle['SubFileName'].replace('-', '.').replace(' ', '.').replace('_', '.').lower().split('.')
  371.         for subPart in subFileParts:
  372.             for filePart in videoFileParts:
  373.                 if subPart == filePart:
  374.                     score += 1
  375.         if score > maxScore:
  376.             maxScore = score
  377.             subtitlesSelected = subtitle['SubFileName']
  378.     return subtitlesSelected
  379. # ==== Check dependencies ======================================================
  380. def dependencyChecker():
  381.     """Check the availability of tools used as dependencies"""
  382.     if opt_gui != 'cli':
  383.         if sys.version_info >= (3, 3):
  384.             for tool in ['gunzip', 'wget']:
  385.                 path = shutil.which(tool)
  386.                 if path is None:
  387.                     superPrint("error", "Missing dependency!", "The <b>'" + tool + "'</b> tool is not available, please install it!")
  388.                     return False
  389.     return True
  390. # ==============================================================================
  391. # ==== Main program (execution starts here) ====================================
  392. # ==============================================================================
  393. ExitCode = 2
  394. # ==== Argument parsing
  395. # Get OpenSubtitlesDownload.py script absolute path
  396. if os.path.isabs(sys.argv[0]):
  397.     scriptPath = sys.argv[0]
  398. else:
  399.     scriptPath = os.getcwd() + "/" + str(sys.argv[0])
  400. # Setup ArgumentParser
  401. parser = argparse.ArgumentParser(prog='OpenSubtitlesDownload.py',
  402.                                  description='Automatically find and download the right subtitles for your favorite videos!',
  403.                                  formatter_class=argparse.RawTextHelpFormatter)
  404. parser.add_argument('--cli', help="Force CLI mode", action='store_true')
  405. parser.add_argument('-g', '--gui', help="Select the GUI you want from: auto, kde, gnome, cli (default: auto)")
  406. parser.add_argument('-l', '--lang', help="Specify the language in which the subtitles should be downloaded (default: eng).\nSyntax:\n-l eng,fre: search in both language\n-l eng -l fre: download both language", nargs='?', action='append')
  407. parser.add_argument('-i', '--skip', help="Skip search if an existing subtitles file is detected", action='store_true')
  408. parser.add_argument('-s', '--search', help="Search mode: hash, filename, hash_then_filename, hash_and_filename (default: hash_then_filename)")
  409. parser.add_argument('-t', '--select', help="Selection mode: manual, default, auto")
  410. parser.add_argument('-a', '--auto', help="Trigger automatic selection and download of the best subtitles found", action='store_true')
  411. parser.add_argument('-o', '--output', help="Override subtitles download path, instead of next their video file")
  412. parser.add_argument('filePathListArg', help="The video file(s) for which subtitles should be searched and downloaded", nargs='+')
  413. # Only use ArgumentParser if we have arguments...
  414. if len(sys.argv) > 1:
  415.     result = parser.parse_args()
  416.     # Handle results
  417.     if result.cli:
  418.         opt_gui = 'cli'
  419.     if result.gui:
  420.         opt_gui = result.gui
  421.     if result.search:
  422.         opt_search_mode = result.search
  423.     if result.skip:
  424.         opt_search_overwrite = 'off'
  425.     if result.select:
  426.         opt_selection_mode = result.select
  427.     if result.auto:
  428.         opt_selection_mode = 'auto'
  429.     if result.output:
  430.         opt_output_path = result.output
  431.     if result.lang:
  432.         if opt_languages != result.lang:
  433.             opt_languages = result.lang
  434.             opt_selection_language = 'on'
  435.             if opt_language_suffix != 'off':
  436.                 opt_language_suffix = 'on'
  437. # GUI auto detection
  438. if opt_gui == 'auto':
  439.     # Note: "ps cax" only output the first 15 characters of the executable's names
  440.     ps = str(subprocess.Popen(['ps', 'cax'], stdout=subprocess.PIPE).communicate()[0]).split('\n')
  441.     for line in ps:
  442.         if ('gnome-session' in line) or ('cinnamon-sessio' in line) or ('mate-session' in line) or ('xfce4-session' in line):
  443.             opt_gui = 'gnome'
  444.             break
  445.         elif 'ksmserver' in line:
  446.             opt_gui = 'kde'
  447.             break
  448. # Sanitize settings
  449. if opt_search_mode not in ['hash', 'filename', 'hash_then_filename', 'hash_and_filename']:
  450.     opt_search_mode = 'hash_then_filename'
  451. if opt_selection_mode not in ['manual', 'default', 'auto']:
  452.     opt_selection_mode = 'default'
  453. if opt_gui not in ['gnome', 'kde', 'cli']:
  454.     opt_gui = 'cli'
  455.     opt_search_mode = 'hash_then_filename'
  456.     opt_selection_mode = 'auto'
  457.     print("Unknown GUI, falling back to an automatic CLI mode")
  458. # ==== Check for the necessary tools (must be done after GUI auto detection)
  459. if dependencyChecker() is False:
  460.     sys.exit(2)
  461. # ==== Get valid video paths
  462. videoPathList = []
  463. if 'result' in locals():
  464.     # Go through the paths taken from arguments, and extract only valid video paths
  465.     for i in result.filePathListArg:
  466.         filePath = os.path.abspath(i)
  467.         if os.path.isdir(filePath):
  468.             # If it is a folder, check all of its files
  469.             for item in os.listdir(filePath):
  470.                 localPath = os.path.join(filePath, item)
  471.                 if checkFileValidity(localPath):
  472.                     videoPathList.append(localPath)
  473.         elif checkFileValidity(filePath):
  474.             # If it is a valid file, use it
  475.             videoPathList.append(filePath)
  476. else:
  477.     superPrint("error", "No file provided!", "No file provided!")
  478.     sys.exit(2)
  479. # If videoPathList is empty, abort!
  480. if not videoPathList:
  481.     parser.print_help()
  482.     sys.exit(1)
  483. # Check if the subtitles files already exists
  484. if opt_search_overwrite == 'off':
  485.     videoPathList = [path for path in videoPathList if not checkSubtitlesExists(path)]
  486.     # If videoPathList is empty, exit!
  487.     if not videoPathList:
  488.         sys.exit(1)
  489. # ==== Instances dispatcher ====================================================
  490. # The first video file will be processed by this instance
  491. videoPath = videoPathList[0]
  492. videoPathList.pop(0)
  493. # The remaining file(s) are dispatched to new instance(s) of this script
  494. for videoPathDispatch in videoPathList:
  495.     # Handle current options
  496.     command = sys.executable + " " + scriptPath + " -g " + opt_gui + " -s " + opt_search_mode + " -t " + opt_selection_mode
  497.     if not (len(opt_languages) == 1 and opt_languages[0] == 'eng'):
  498.         for resultlangs in opt_languages:
  499.             command += " -l " + resultlangs
  500.     # Split command string
  501.     command_splitted = command.split()
  502.     # The videoPath filename can contain spaces, but we do not want to split that, so add it right after the split
  503.     command_splitted.append(videoPathDispatch)
  504.     # Do not spawn too many instances at once
  505.     time.sleep(0.33)
  506.     if opt_gui == 'cli' and opt_selection_mode != 'auto':
  507.         # Synchronous call
  508.         process_videoDispatched = subprocess.call(command_splitted)
  509.     else:
  510.         # Asynchronous call
  511.         process_videoDispatched = subprocess.Popen(command_splitted)
  512. # ==== Search and download subtitles ===========================================
  513. try:
  514.     # ==== Connection to OpenSubtitlesDownload
  515.     try:
  516.         session = osd_server.LogIn(osd_username, osd_password, osd_language, 'opensubtitles-download 4.1')
  517.     except Exception:
  518.         # Retry once after a delay (could just be a momentary overloaded server?)
  519.         time.sleep(3)
  520.         try:
  521.             session = osd_server.LogIn(osd_username, osd_password, osd_language, 'opensubtitles-download 4.1')
  522.         except Exception:
  523.             superPrint("error", "Connection error!", "Unable to reach opensubtitles.org servers!\n\nPlease check:\n- Your Internet connection status\n- www.opensubtitles.org availability\n- Your downloads limit (200 subtitles per 24h)\n\nThe subtitles search and download service is powered by opensubtitles.org. Be sure to donate if you appreciate the service provided!")
  524.             sys.exit(2)
  525.     # Connection refused?
  526.     if session['status'] != '200 OK':
  527.         superPrint("error", "Connection error!", "Opensubtitles.org servers refused the connection: " + session['status'] + ".\n\nPlease check:\n- Your Internet connection status\n- www.opensubtitles.org availability\n- Your downloads limit (200 subtitles per 24h)\n\nThe subtitles search and download service is powered by opensubtitles.org. Be sure to donate if you appreciate the service provided!")
  528.         sys.exit(2)
  529.     # Count languages marked for this search
  530.     searchLanguage = 0
  531.     searchLanguageResult = 0
  532.     for SubLanguageID in opt_languages:
  533.         searchLanguage += len(SubLanguageID.split(','))
  534.     searchResultPerLanguage = [searchLanguage]
  535.     # ==== Get file hash, size and name
  536.     videoTitle = ''
  537.     videoHash = hashFile(videoPath)
  538.     videoSize = os.path.getsize(videoPath)
  539.     videoFileName = os.path.basename(videoPath)
  540.     # ==== Search for available subtitles on OpenSubtitlesDownload
  541.     for SubLanguageID in opt_languages:
  542.         searchList = []
  543.         subtitlesList = {}
  544.         if opt_search_mode in ('hash', 'hash_then_filename', 'hash_and_filename'):
  545.             searchList.append({'sublanguageid':SubLanguageID, 'moviehash':videoHash, 'moviebytesize':str(videoSize)})
  546.         if opt_search_mode in ('filename', 'hash_and_filename'):
  547.             searchList.append({'sublanguageid':SubLanguageID, 'query':videoFileName})
  548.         ## Primary search
  549.         try:
  550.             subtitlesList = osd_server.SearchSubtitles(session['token'], searchList)
  551.         except Exception:
  552.             # Retry once after a delay (we are already connected, the server may be momentary overloaded)
  553.             time.sleep(3)
  554.             try:
  555.                 subtitlesList = osd_server.SearchSubtitles(session['token'], searchList)
  556.             except Exception:
  557.                 superPrint("error", "Search error!", "Unable to reach opensubtitles.org servers!\n<b>Search error</b>")
  558.         #if (opt_search_mode == 'hash_and_filename'):
  559.         #    TODO Cleanup duplicate between moviehash and filename results
  560.         ## Fallback search
  561.         if ((opt_search_mode == 'hash_then_filename') and (('data' in subtitlesList) and (not subtitlesList['data']))):
  562.             searchList[:] = [] # searchList.clear()
  563.             searchList.append({'sublanguageid':SubLanguageID, 'query':videoFileName})
  564.             subtitlesList.clear()
  565.             try:
  566.                 subtitlesList = osd_server.SearchSubtitles(session['token'], searchList)
  567.             except Exception:
  568.                 # Retry once after a delay (we are already connected, the server may be momentary overloaded)
  569.                 time.sleep(3)
  570.                 try:
  571.                     subtitlesList = osd_server.SearchSubtitles(session['token'], searchList)
  572.                 except Exception:
  573.                     superPrint("error", "Search error!", "Unable to reach opensubtitles.org servers!\n<b>Search error</b>")
  574.         ## Parse the results of the XML-RPC query
  575.         if ('data' in subtitlesList) and (subtitlesList['data']):
  576.             # Mark search as successful
  577.             searchLanguageResult += 1
  578.             subtitlesSelected = ''
  579.             # If there is only one subtitles (matched by file hash), auto-select it (except in CLI mode)
  580.             if (len(subtitlesList['data']) == 1) and (subtitlesList['data'][0]['MatchedBy'] == 'moviehash'):
  581.                 if opt_selection_mode != 'manual':
  582.                     subtitlesSelected = subtitlesList['data'][0]['SubFileName']
  583.             # Get video title
  584.             videoTitle = subtitlesList['data'][0]['MovieName']
  585.             # Title and filename may need string sanitizing to avoid zenity/kdialog handling errors
  586.             if opt_gui != 'cli':
  587.                 videoTitle = videoTitle.replace('"', '\\"')
  588.                 videoTitle = videoTitle.replace("'", "\'")
  589.                 videoTitle = videoTitle.replace('`', '\`')
  590.                 videoTitle = videoTitle.replace("&", "&")
  591.                 videoFileName = videoFileName.replace('"', '\\"')
  592.                 videoFileName = videoFileName.replace("'", "\'")
  593.                 videoFileName = videoFileName.replace('`', '\`')
  594.                 videoFileName = videoFileName.replace("&", "&")
  595.             # If there is more than one subtitles and opt_selection_mode != 'auto',
  596.             # then let the user decide which one will be downloaded
  597.             if not subtitlesSelected:
  598.                 # Automatic subtitles selection?
  599.                 if opt_selection_mode == 'auto':
  600.                     subtitlesSelected = selectionAuto(subtitlesList)
  601.                 else:
  602.                     # Go through the list of subtitles and handle 'auto' settings activation
  603.                     for item in subtitlesList['data']:
  604.                         if opt_selection_match == 'auto':
  605.                             if opt_search_mode == 'hash_and_filename':
  606.                                 opt_selection_match = 'on'
  607.                         if opt_selection_language == 'auto':
  608.                             if searchLanguage > 1:
  609.                                 opt_selection_language = 'on'
  610.                         if opt_selection_hi == 'auto':
  611.                             if item['SubHearingImpaired'] == '1':
  612.                                 opt_selection_hi = 'on'
  613.                         if opt_selection_rating == 'auto':
  614.                             if item['SubRating'] != '0.0':
  615.                                 opt_selection_rating = 'on'
  616.                         if opt_selection_count == 'auto':
  617.                             opt_selection_count = 'on'
  618.                     # Spaw selection window
  619.                     if opt_gui == 'gnome':
  620.                         subtitlesSelected = selectionGnome(subtitlesList)
  621.                     elif opt_gui == 'kde':
  622.                         subtitlesSelected = selectionKde(subtitlesList)
  623.                     else: # CLI
  624.                         subtitlesSelected = selectionCLI(subtitlesList)
  625.             # If a subtitles has been selected at this point, download it!
  626.             if subtitlesSelected:
  627.                 subIndex = 0
  628.                 subIndexTemp = 0
  629.                 # Select the subtitles file to download
  630.                 for item in subtitlesList['data']:
  631.                     if item['SubFileName'] == subtitlesSelected:
  632.                         subIndex = subIndexTemp
  633.                         break
  634.                     else:
  635.                         subIndexTemp += 1
  636.                 subLangId = opt_language_separator  + subtitlesList['data'][subIndex]['ISO639']
  637.                 subLangName = subtitlesList['data'][subIndex]['LanguageName']
  638.                 subURL = subtitlesList['data'][subIndex]['SubDownloadLink']
  639.                 subEncoding = subtitlesList['data'][subIndex]['SubEncoding']
  640.                 subPath = videoPath.rsplit('.', 1)[0] + '.' + subtitlesList['data'][subIndex]['SubFormat']
  641.                 if opt_output_path and os.path.isdir(os.path.abspath(opt_output_path)):
  642.                     subPath = os.path.abspath(opt_output_path) + "/" + subPath.rsplit('/', 1)[1]
  643.                 # Write language code into the filename?
  644.                 if ((opt_language_suffix == 'on') or (opt_language_suffix == 'auto' and searchLanguageResult > 1)):
  645.                     subPath = videoPath.rsplit('.', 1)[0] + subLangId + '.' + subtitlesList['data'][subIndex]['SubFormat']
  646.                 # Escape non-alphanumeric characters from the subtitles path
  647.                 if opt_gui != 'cli':
  648.                     subPath = re.escape(subPath)
  649.                 # Make sure we are downloading an UTF8 encoded file
  650.                 downloadPos = subURL.find("download/")
  651.                 if downloadPos > 0:
  652.                     subURL = subURL[:downloadPos+9] + "subencoding-utf8/" + subURL[downloadPos+9:]
  653.                 ## Download and unzip the selected subtitles (with progressbar)
  654.                 if opt_gui == 'gnome':
  655.                     process_subtitlesDownload = subprocess.call("(wget -q -O - " + subURL + " | gunzip > " + subPath + ") 2>&1" + ' | (zenity --auto-close --progress --pulsate --title="Downloading subtitles, please wait..." --text="Downloading <b>' + subtitlesList['data'][subIndex]['LanguageName'] + '</b> subtitles for <b>' + videoTitle + '</b>...")', shell=True)
  656.                 elif opt_gui == 'kde':
  657.                     process_subtitlesDownload = subprocess.call("(wget -q -O - " + subURL + " | gunzip > " + subPath + ") 2>&1", shell=True)
  658.                 else: # CLI
  659.                     print(">> Downloading '" + subtitlesList['data'][subIndex]['LanguageName'] + "' subtitles for '" + videoTitle + "'")
  660.                     if sys.version_info >= (3, 0):
  661.                         tmpFile1, headers = urllib.request.urlretrieve(subURL)
  662.                         tmpFile2 = gzip.GzipFile(tmpFile1)
  663.                         byteswritten = open(subPath, 'wb').write(tmpFile2.read())
  664.                         if byteswritten > 0:
  665.                             process_subtitlesDownload = 0
  666.                         else:
  667.                             process_subtitlesDownload = 1
  668.                     else: # python 2
  669.                         tmpFile1, headers = urllib.urlretrieve(subURL)
  670.                         tmpFile2 = gzip.GzipFile(tmpFile1)
  671.                         open(subPath, 'wb').write(tmpFile2.read())
  672.                         process_subtitlesDownload = 0
  673.                 # If an error occurs, say so
  674.                 if process_subtitlesDownload != 0:
  675.                     superPrint("error", "Subtitling error!", "An error occurred while downloading or writing <b>" + subtitlesList['data'][subIndex]['LanguageName'] + "</b> subtitles for <b>" + videoTitle + "</b>.")
  676.                     osd_server.LogOut(session['token'])
  677.                     sys.exit(2)
  678.     ## Print a message if no subtitles have been found, for any of the languages
  679.     if searchLanguageResult == 0:
  680.         superPrint("info", "No subtitles available :-(", '<b>No subtitles found</b> for this video:\n<i>' + videoFileName + '</i>')
  681.         ExitCode = 1
  682.     else:
  683.         ExitCode = 0
  684. except (OSError, IOError, RuntimeError, TypeError, NameError, KeyError):
  685.     # Do not warn about remote disconnection # bug/feature of python 3.5?
  686.     if "http.client.RemoteDisconnected" in str(sys.exc_info()[0]):
  687.         sys.exit(ExitCode)
  688.     # An unknown error occur, let's apologize before exiting
  689.     superPrint("error", "Unexpected error!", "OpenSubtitlesDownload encountered an <b>unknown error</b>, sorry about that...\n\n" + \
  690.                "Error: <b>" + str(sys.exc_info()[0]).replace('<', '[').replace('>', ']') + "</b>\n" + \
  691.                "Line: <b>" + str(sys.exc_info()[-1].tb_lineno) + "</b>\n\n" + \
  692.                "Just to be safe, please check:\n- www.opensubtitles.org availability\n- Your downloads limit (200 subtitles per 24h)\n- Your Internet connection status\n- That are using the latest version of this software ;-)")
  693. except Exception:
  694.     # Catch unhandled exceptions but do not spawn an error window
  695.     print("Unexpected error (line " + str(sys.exc_info()[-1].tb_lineno) + "): " + str(sys.exc_info()[0]))
  696. # Disconnect from opensubtitles.org server, then exit
  697. if session and session['token']:
  698.     osd_server.LogOut(session['token'])
  699. sys.exit(ExitCode)

Все что ему нужно — указать файл для которого мы хотим найти сабы, и пару параметров отвечающих за сами сабы (язык, обновлять не обновлять и т.д.). Так как мы исходим из того, что торрент-файлы у нас качаются автоматически, то и этот скрипт применять к файлам лучше скриптом, добавленным в крон. Скрипт простецкий:

/etc/cron.daily/download-subtitle
 
  1. #!/bin/bash
  2. path="/share/torrents/xa/"
  3. download_sub="/opt/OpenSubtitlesDownload.py --cli --lang eng --lang rus --skip  --auto "
  4. rmd="rm -rf "
  5. find "${path}" -size  50M -type f  -exec ${download_sub} {} \;
  6. find "${path}" -empty -type d  -exec ${rmd} {} \;

Этот скрипт проверяет все файлы в нашем каталоге /share/torrents/xa/, находит файлы больше 50 мегабайт (потому что иногда в торрентах содержится не только видеофайл, но и какой-нибудь сопровождающий файл с описанием релиза, да и сами субтитры, которые скачались в прошлый раз, нас не интересуют) и натравливает на каждый их них скрипт поиска субтитров. Если субтитры указанных языков (русский английский) найдены — они скачиваются. Также скрипт удаляет пустые каталоги, которые иногда образуются лично у меня после переноса новых серий на постоянное место жительства.

В итоге

Мы получаем уютный сервачок, который сам по себе живет и поставляет к нашему столу свежие серии. Работает исправно на все 95%, осечки случаются, но как правило это связано с некорректным названием торрентов (рукожопы случаются) или отсутствием субтитров на opensubtitles (если я нахожу сабы на стороне, стараюсь их добавить и туда).

fail2ban не добавляет правила nftables (Ubuntu 16), чиним!

Внезапно — в Ubuntu 16 корявый пакет fail2ban (10.2), которому не доложили конфигов! Поэтому ключевое:

 
 
  1. # идем куда нeдосыпали
  2. cd /etc/fail2ban/action.d/
  3. # бэкапим штатные (хоть и кривые) конфиги
  4. mv nftables-multiport.conf nftables-multiport.conf.bak
  5. mv nftables-allports.conf nftables-allports.conf.bak
  6. # качаем правильные конфиги
  7. wget https://raw.githubusercontent.com/fail2ban/fail2ban/master/config/action.d/nftables-allports.conf
  8. wget https://raw.githubusercontent.com/fail2ban/fail2ban/master/config/action.d/nftables-multiport.conf
  9. # вот этого вообще не было!:
  10. wget https://raw.githubusercontent.com/fail2ban/fail2ban/master/config/action.d/nftables.conf

 

Что я делал дальше:

  1. Убедился что у меня есть штатный конфиг «/etc/fail2ban/jail.conf», полученный копированием «jail.conf.dpkg-dist» с последующим вырезанием секции с тюрьмами (для них у меня «/jail.d/» есть и незачем их смешивать)
    /etc/fail2ban/jail.conf
     
    1. #
    2. # WARNING: heavily refactored in 0.9.0 release. Please review and
    3. # customize settings for your setup.
    4. #
    5. # Changes: in most of the cases you should not modify this
    6. # file, but provide customizations in jail.local file,
    7. # or separate .conf files under jail.d/ directory, e.g.:
    8. #
    9. # HOW TO ACTIVATE JAILS:
    10. #
    11. # YOU SHOULD NOT MODIFY THIS FILE.
    12. #
    13. # It will probably be overwritten or improved in a distribution update.
    14. #
    15. # Provide customizations in a jail.local file or a jail.d/customisation.local.
    16. # For example to change the default bantime for all jails and to enable the
    17. # ssh-iptables jail the following (uncommented) would appear in the .local file.
    18. # See man 5 jail.conf for details.
    19. #
    20. # [DEFAULT]
    21. # bantime = 1h
    22. #
    23. # [sshd]
    24. # enabled = true
    25. #
    26. # See jail.conf(5) man page for more information
    27. # Comments: use '#' for comment lines and ';' (following a space) for inline comments
    28. [INCLUDES]
    29. #before = paths-distro.conf
    30. before = paths-debian.conf
    31. # The DEFAULT allows a global definition of the options. They can be overridden
    32. # in each jail afterwards.
    33. [DEFAULT]
    34. #
    35. # MISCELLANEOUS OPTIONS
    36. #
    37. # "ignorself" specifies whether the local resp. own IP addresses should be ignored
    38. # (default is true). Fail2ban will not ban a host which matches such addresses.
    39. #ignorself = true
    40. # "ignoreip" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban
    41. # will not ban a host which matches an address in this list. Several addresses
    42. # can be defined using space (and/or comma) separator.
    43. #ignoreip = 127.0.0.1/8 ::1
    44. # External command that will take an tagged arguments to ignore, e.g. <ip>,
    45. # and return true if the IP is to be ignored. False otherwise.
    46. #
    47. # ignorecommand = /path/to/command <ip>
    48. ignorecommand =
    49. # "bantime" is the number of seconds that a host is banned.
    50. bantime = 10m
    51. # A host is banned if it has generated "maxretry" during the last "findtime"
    52. # seconds.
    53. findtime = 10m
    54. # "maxretry" is the number of failures before a host get banned.
    55. maxretry = 5
    56. # "backend" specifies the backend used to get files modification.
    57. # Available options are "pyinotify", "gamin", "polling", "systemd" and "auto".
    58. # This option can be overridden in each jail as well.
    59. #
    60. # pyinotify: requires pyinotify (a file alteration monitor) to be installed.
    61. # If pyinotify is not installed, Fail2ban will use auto.
    62. # gamin: requires Gamin (a file alteration monitor) to be installed.
    63. # If Gamin is not installed, Fail2ban will use auto.
    64. # polling: uses a polling algorithm which does not require external libraries.
    65. # systemd: uses systemd python library to access the systemd journal.
    66. # Specifying "logpath" is not valid for this backend.
    67. # See "journalmatch" in the jails associated filter config
    68. # auto: will try to use the following backends, in order:
    69. # pyinotify, gamin, polling.
    70. #
    71. # Note: if systemd backend is chosen as the default but you enable a jail
    72. # for which logs are present only in its own log files, specify some other
    73. # backend for that jail (e.g. polling) and provide empty value for
    74. # journalmatch. See https://github.com/fail2ban/fail2ban/issues/959#issuecomment-74901200
    75. backend = auto
    76. # "usedns" specifies if jails should trust hostnames in logs,
    77. # warn when DNS lookups are performed, or ignore all hostnames in logs
    78. #
    79. # yes: if a hostname is encountered, a DNS lookup will be performed.
    80. # warn: if a hostname is encountered, a DNS lookup will be performed,
    81. # but it will be logged as a warning.
    82. # no: if a hostname is encountered, will not be used for banning,
    83. # but it will be logged as info.
    84. # raw: use raw value (no hostname), allow use it for no-host filters/actions (example user)
    85. usedns = warn
    86. # "logencoding" specifies the encoding of the log files handled by the jail
    87. # This is used to decode the lines from the log file.
    88. # Typical examples: "ascii", "utf-8"
    89. #
    90. # auto: will use the system locale setting
    91. logencoding = auto
    92. # "enabled" enables the jails.
    93. # By default all jails are disabled, and it should stay this way.
    94. # Enable only relevant to your setup jails in your .local or jail.d/*.conf
    95. #
    96. # true: jail will be enabled and log files will get monitored for changes
    97. # false: jail is not enabled
    98. enabled = false
    99. # "mode" defines the mode of the filter (see corresponding filter implementation for more info).
    100. mode = normal
    101. # "filter" defines the filter to use by the jail.
    102. # By default jails have names matching their filter name
    103. #
    104. filter = %(__name__)s[mode=%(mode)s]
    105. #
    106. # ACTIONS
    107. #
    108. # Some options used for actions
    109. # Destination email address used solely for the interpolations in
    110. # jail.{conf,local,d/*} configuration files.
    111. destemail = root@localhost
    112. # Sender email address used solely for some actions
    113. sender = root@<fq-hostname>
    114. # E-mail action. Since 0.8.1 Fail2Ban uses sendmail MTA for the
    115. # mailing. Change mta configuration parameter to mail if you want to
    116. # revert to conventional 'mail'.
    117. mta = sendmail
    118. # Default protocol
    119. protocol = tcp
    120. # Specify chain where jumps would need to be added in ban-actions expecting parameter chain
    121. chain = <known/chain>
    122. # Ports to be banned
    123. # Usually should be overridden in a particular jail
    124. port = 0:65535
    125. # Format of user-agent https://tools.ietf.org/html/rfc7231#section-5.5.3
    126. fail2ban_agent = Fail2Ban/%(fail2ban_version)s
    127. #
    128. # Action shortcuts. To be used to define action parameter
    129. # Default banning action (e.g. iptables, iptables-new,
    130. # iptables-multiport, shorewall, etc) It is used to define
    131. # action_* variables. Can be overridden globally or per
    132. # section within jail.local file
    133. banaction = nftables-multiport
    134. banaction_allports = nftables-allports
    135. # The simplest action to take: ban only
    136. action_ = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
    137. # ban & send an e-mail with whois report to the destemail.
    138. action_mw = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
    139. %(mta)s-whois[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]
    140. # ban & send an e-mail with whois report and relevant log lines
    141. # to the destemail.
    142. action_mwl = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
    143. %(mta)s-whois-lines[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]
    144. # See the IMPORTANT note in action.d/xarf-login-attack for when to use this action
    145. #
    146. # ban & send a xarf e-mail to abuse contact of IP address and include relevant log lines
    147. # to the destemail.
    148. action_xarf = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
    149. xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath=%(logpath)s, port="%(port)s"]
    150. # ban IP on CloudFlare & send an e-mail with whois report and relevant log lines
    151. # to the destemail.
    152. action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"]
    153. %(mta)s-whois-lines[name=%(__name__)s, sender="%(sender)s", dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]
    154. # Report block via blocklist.de fail2ban reporting service API
    155. #
    156. # See the IMPORTANT note in action.d/blocklist_de.conf for when to use this action.
    157. # Specify expected parameters in file action.d/blocklist_de.local or if the interpolation
    158. # `action_blocklist_de` used for the action, set value of `blocklist_de_apikey`
    159. # in your `jail.local` globally (section [DEFAULT]) or per specific jail section (resp. in
    160. # corresponding jail.d/my-jail.local file).
    161. #
    162. action_blocklist_de = blocklist_de[email="%(sender)s", service=%(filter)s, apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"]
    163. # Report ban via badips.com, and use as blacklist
    164. #
    165. # See BadIPsAction docstring in config/action.d/badips.py for
    166. # documentation for this action.
    167. #
    168. # NOTE: This action relies on banaction being present on start and therefore
    169. # should be last action defined for a jail.
    170. #
    171. action_badips = badips.py[category="%(__name__)s", banaction="%(banaction)s", agent="%(fail2ban_agent)s"]
    172. #
    173. # Report ban via badips.com (uses action.d/badips.conf for reporting only)
    174. #
    175. action_badips_report = badips[category="%(__name__)s", agent="%(fail2ban_agent)s"]
    176. # Report ban via abuseipdb.com.
    177. #
    178. # See action.d/abuseipdb.conf for usage example and details.
    179. #
    180. action_abuseipdb = abuseipdb
    181. # Choose default action. To change, just override value of 'action' with the
    182. # interpolation to the chosen action shortcut (e.g. action_mw, action_mwl, etc) in jail.local
    183. # globally (section [DEFAULT]) or per specific section
    184. action = %(action_)s
  2. Создал файл «/etc/fail2ban/jail.local», который читается после «/etc/fail2ban/jail.conf» и служит для переопределения параметров
    /etc/fail2ban/jail.local
     
    1. [DEFAULT]
    2. banaction = nftables-multiport
    3. banaction_allports = nftables-allports

Все! В инете есть еще варианты назначить конкретную таблицу для правил f2b, но вот с такой конфигурацией он и сам прекрасно добавляет таблицу имени себя:

 
 
  1. table inet f2b-table {
  2.         set addr-set-sshd {
  3.                 type ipv4_addr
  4.                 elements = { 31.220.1.210, 37.49.226.161 }
  5.         }
  6.         set addr-set-wp-qiwichupa-net {
  7.                 type ipv4_addr
  8.                 elements = { 34.76.172.157, 34.82.91.206,
  9.                              35.183.87.236, 35.220.162.15 }
  10.         }
  11.         chain f2b-chain {
  12.                 type filter hook input priority -1; policy accept;
  13.                 tcp dport { ssh } ip saddr @addr-set-sshd reject
  14.                 tcp dport { http, https } ip saddr @addr-set-wp-qiwichupa-net reject
  15.         }
  16. }

Единственное что имеет смысл, создать файлик /etc/fail2ban/action.d/nftables-common.local:

 
 
  1. [Init]
  2. blocktype = drop

Он также будет читаться как конфиг nftables и заставит дропать, а не реджектить пакеты с заблокированных айпишников.

И на эту дичь было убито 4 часа -_-

Разворачивание syslog-сервера с веб-интерфейсом

Невероятно странно, но попытки нагуглить хоть какой-нибудь веб-интерфейс для сислога приводят в основном в дебри топов коммерческих интерфейсов, среди которых, может, и есть бесплатные версии, но будьте любезны сами ковыряться и  разбираться насколько оно убого или нет, и какой функционал там порезан.

Мне же от вебморды много не надо — мне надо чтобы она лог показывала,  фильтры имела, и было бы неплохо чтобы составляла с сервером полностью FOSS-решение. И сам я давно уже использую под это дело LogAnalyzer, который с поставленной задачей — презентовать лог сислог-сервера, собирающего в базу данных логи с различного оборудования — справляется весьма неплохо.

И так как в целом задача развертывания сислог-сервера с веб-мордой не решается автоматически установкой какого-нибудь пакета в этом нашем дебиане, я решил во-первых написать скрипт, который сделает все за вас, ну а попутно еще расписать как в целом все это инсталлируется и настраивается руками.

Итак, скрипт писался и проверялся на Debian 10 «Buster», его актуальная версия находится на гитхабе, а текущую я оставлю здесь:

loganalyzer_install.sh
 
  1. #!/bin/bash
  2. # Syslog server with web interface for Debian 10
  3. # LAMP + phpmyadmin + rsyslog + Log Analyzer
  4. PHPMYADMINUSER="pma"
  5. PHPMYADMINPASS="321"
  6. SYSLOGDBPASSWORD="Qwerty"
  7. PMAVER="4.9.2"
  8. LAVERSION="4.1.7"
  9. export LANG="en_US.UTF-8"
  10. function check_packages {
  11.     notinstalled=""
  12.     if [ $# -eq 0 ]; then echo "Package name(s) required"; fi
  13.     if [ $# -gt 0 ]; then
  14.         for packagename in $@; do
  15.             if [[ "" == $(dpkg-query  -W --showformat='${Status}\n' ${packagename}  2>&1 | grep "install ok") ]]; then
  16.                 notinstalled=${notinstalled}${packagename}" "
  17.             fi
  18.         done
  19.     fi
  20.     if [[ "" == ${notinstalled} ]]; then
  21.         echo "true"
  22.     else
  23.         echo "${notinstalled}"
  24.     fi
  25. }
  26. ######## MARIA DB
  27. function check_sql {
  28.     if [[ "true" != $( check_packages  mariadb-server ) && "true" != $( check_packages  mysql-server) ]]; then
  29.         echo "false"
  30.     fi
  31.     if  [[ "true" == $( check_packages  mariadb-server ) ]]; then echo "mariadb"; fi
  32.     if  [[ "true" == $( check_packages  mysql-server ) ]]; then echo "mysql"; fi
  33. }
  34. function install_sql {
  35.     apt -y install mariadb-server
  36. #    mysql -uroot -e "UPDATE mysql.user SET Password=PASSWORD('${MYSQLROOTPASS}') WHERE User='root';\
  37. #    DELETE FROM mysql.user WHERE User='';\
  38. #    DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');\
  39. #    DROP DATABASE test;\
  40. #    DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%';\
  41. #    FLUSH PRIVILEGES;"
  42. }
  43. ######### PHP
  44. function check_php {
  45.     phpnotinstalled=$( check_packages php libapache2-mod-php php-mysql php-common php-cli  php-json php-gd php-opcache php-readline php-mbstring )
  46.     if [[ "true" != ${phpnotinstalled} ]]; then
  47.         echo "${phpnotinstalled}"
  48.     else
  49.         echo "true"
  50.     fi
  51. }
  52. ######### PHPMYADMIN
  53. function check_phpmyadmin {
  54.     if [[ ! -d /usr/share/phpmyadmin/ ]]; then
  55.         echo "false"
  56.     fi
  57. }
  58. function install_phpmyadmin {
  59. IFS='' read -r -d '' SITECONFIG  <<"EOF"
  60. Alias /phpmyadmin /usr/share/phpmyadmin
  61. <Directory /usr/share/phpmyadmin>
  62.     Options SymLinksIfOwnerMatch
  63.     DirectoryIndex index.php
  64.     <IfModule mod_php5.c>
  65.         <IfModule mod_mime.c>
  66.             AddType application/x-httpd-php .php
  67.         </IfModule>
  68.         <FilesMatch ".+\.php$">
  69.             SetHandler application/x-httpd-php
  70.         </FilesMatch>
  71.         php_value include_path .
  72.         php_admin_value upload_tmp_dir /var/lib/phpmyadmin/tmp
  73.         php_admin_value open_basedir /usr/share/phpmyadmin/:/etc/phpmyadmin/:/var/lib/phpmyadmin/:/usr/share/php/php-gettext/:/usr/share/php/php-php-gettext/:/usr/share/javascript/:/usr/share/php/tcpdf/:/usr/share/doc/phpmyadmin/:/usr/share/php/phpseclib/
  74.         php_admin_value mbstring.func_overload 0
  75.     </IfModule>
  76.     <IfModule mod_php.c>
  77.         <IfModule mod_mime.c>
  78.             AddType application/x-httpd-php .php
  79.         </IfModule>
  80.         <FilesMatch ".+\.php$">
  81.             SetHandler application/x-httpd-php
  82.         </FilesMatch>
  83.         php_value include_path .
  84.         php_admin_value upload_tmp_dir /var/lib/phpmyadmin/tmp
  85.         php_admin_value open_basedir /usr/share/phpmyadmin/:/etc/phpmyadmin/:/var/lib/phpmyadmin/:/usr/share/php/php-gettext/:/usr/share/php/php-php-gettext/:/usr/share/javascript/:/usr/share/php/tcpdf/:/usr/share/doc/phpmyadmin/:/usr/share/php/phpseclib/
  86.         php_admin_value mbstring.func_overload 0
  87.     </IfModule>
  88. </Directory>
  89. # Authorize for setup
  90. <Directory /usr/share/phpmyadmin/setup>
  91.     <IfModule mod_authz_core.c>
  92.         <IfModule mod_authn_file.c>
  93.             AuthType Basic
  94.             AuthName "phpMyAdmin Setup"
  95.             AuthUserFile /etc/phpmyadmin/htpasswd.setup
  96.         </IfModule>
  97.         Require valid-user
  98.     </IfModule>
  99. </Directory>
  100. # Disallow web access to directories that don't need it
  101. <Directory /usr/share/phpmyadmin/templates>
  102.     Require all denied
  103. </Directory>
  104. <Directory /usr/share/phpmyadmin/libraries>
  105.     Require all denied
  106. </Directory>
  107. <Directory /usr/share/phpmyadmin/setup/lib>
  108.     Require all denied
  109. </Directory>
  110. EOF
  111. if [ ! -f phpMyAdmin-${PMAVER}-all-languages.tar.gz ]; then wget https://files.phpmyadmin.net/phpMyAdmin/${PMAVER}/phpMyAdmin-${PMAVER}-all-languages.tar.gz; fi
  112. tar -xf phpMyAdmin-${PMAVER}-all-languages.tar.gz
  113. mkdir /usr/share/phpmyadmin
  114. cp -r phpMyAdmin-${PMAVER}-all-languages/* /usr/share/phpmyadmin
  115. mkdir -p /var/lib/phpmyadmin/tmp
  116. chown -R www-data:www-data /var/lib/phpmyadmin
  117. mkdir /etc/phpmyadmin/
  118. cp /usr/share/phpmyadmin/config.sample.inc.php  /usr/share/phpmyadmin/config.inc.php
  119. ln -s /usr/share/phpmyadmin/config.inc.php /etc/phpmyadmin/
  120. echo "\$cfg['TempDir'] ='/var/lib/phpmyadmin/tmp';" >> /usr/share/phpmyadmin/config.inc.php
  121. r="\$cfg\['blowfish_secret'\] = '"
  122. sed -i  "s/${r}/${r}$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32)/g" /usr/share/phpmyadmin/config.inc.php
  123. echo "$SITECONFIG" >  /etc/apache2/sites-available/phpmyadmin.conf
  124. ln -s /etc/apache2/sites-available/phpmyadmin.conf /etc/apache2/sites-enabled/
  125. service  apache2 restart
  126. mysql -uroot -e "use mysql; CREATE USER ${PHPMYADMINUSER}@localhost IDENTIFIED BY '${PHPMYADMINPASS}'; GRANT ALL ON *.* TO ${PHPMYADMINUSER}@localhost WITH GRANT OPTION;"
  127. service mysql restart
  128. }
  129. ######### SYSLOG
  130. function install_rsyslog_mysql {
  131. debconf-set-selections << END
  132. rsyslog-mysql      rsyslog-mysql/mysql/admin-pass      password
  133. # MySQL application password for rsyslog-mysql:
  134. rsyslog-mysql      rsyslog-mysql/mysql/app-pass      password ${SYSLOGDBPASSWORD}
  135. rsyslog-mysql      rsyslog-mysql/password-confirm      password ${SYSLOGDBPASSWORD}
  136. rsyslog-mysql      rsyslog-mysql/app-password-confirm      password ${SYSLOGDBPASSWORD}
  137. # MySQL username for rsyslog-mysql:
  138. rsyslog-mysql      rsyslog-mysql/db/app-user      string      rsyslog@localhost
  139. # Back up the database for rsyslog-mysql before upgrading?
  140. rsyslog-mysql      rsyslog-mysql/upgrade-backup      boolean      true
  141. # Host running the MySQL server for rsyslog-mysql:
  142. rsyslog-mysql      rsyslog-mysql/remote/newhost      string
  143. # MySQL database name for rsyslog-mysql:
  144. rsyslog-mysql      rsyslog-mysql/db/dbname      string      Syslog
  145. # Reinstall database for rsyslog-mysql?
  146. rsyslog-mysql      rsyslog-mysql/dbconfig-reinstall      boolean      false
  147. rsyslog-mysql      rsyslog-mysql/missing-db-package-error      select      abort
  148. rsyslog-mysql      rsyslog-mysql/remote/port      string
  149. # Perform upgrade on database for rsyslog-mysql with dbconfig-common?
  150. rsyslog-mysql      rsyslog-mysql/dbconfig-upgrade      boolean      true
  151. # Host name of the MySQL database server for rsyslog-mysql:
  152. rsyslog-mysql      rsyslog-mysql/remote/host      select      localhost
  153. # Deconfigure database for rsyslog-mysql with dbconfig-common?
  154. rsyslog-mysql      rsyslog-mysql/dbconfig-remove      boolean      true
  155. # Database type to be used by rsyslog-mysql:
  156. rsyslog-mysql      rsyslog-mysql/database-type      select      mysql
  157. rsyslog-mysql      rsyslog-mysql/upgrade-error      select      abort
  158. rsyslog-mysql      rsyslog-mysql/remove-error      select      abort
  159. rsyslog-mysql      rsyslog-mysql/install-error      select      abort
  160. rsyslog-mysql      rsyslog-mysql/passwords-do-not-match      error
  161. rsyslog-mysql      rsyslog-mysql/internal/skip-preseed      boolean      false
  162. # Delete the database for rsyslog-mysql?
  163. rsyslog-mysql      rsyslog-mysql/purge      boolean      false
  164. # Connection method for MySQL database of rsyslog-mysql:
  165. rsyslog-mysql      rsyslog-mysql/mysql/method      select      Unix socket
  166. rsyslog-mysql      rsyslog-mysql/mysql/admin-user      string      root
  167. rsyslog-mysql      rsyslog-mysql/internal/reconfiguring      boolean      false
  168. # Configure database for rsyslog-mysql with dbconfig-common?
  169. rsyslog-mysql      rsyslog-mysql/dbconfig-install      boolean      true
  170. END
  171. DEBIAN_FRONTEND=noninteractive apt-get install -y rsyslog-mysql
  172. echo "Creating /etc/rsyslog.d/enable-remote.conf"
  173. echo "module(load=\"imudp\")" > /etc/rsyslog.d/enable-remote.conf
  174. echo "input(type=\"imudp\" port=\"514\")" >> /etc/rsyslog.d/enable-remote.conf
  175. echo "module(load=\"imtcp\")" >> /etc/rsyslog.d/enable-remote.conf
  176. echo "input(type=\"imtcp\" port=\"514\")" >> /etc/rsyslog.d/enable-remote.conf
  177. service rsyslog restart
  178. echo "17 2     * * *     root mysql -uroot -e 'use Syslog; DELETE FROM SystemEvents WHERE ReceivedAt < DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 365 DAY);'" > /etc/cron.d/rsyslog_mysql
  179. #mysql -uroot -e "CREATE DATABASE Syslog_template;"
  180. #mysqldump -uroot Syslog > /tmp/sql.sql
  181. #mysql -uroot Syslog_template < /tmp/sql.sql
  182. #mysql -uroot -e "TRUNCATE TABLE Syslog_template.SystemEvents"
  183. }
  184. ######### LOG ANALYZER
  185. function install_loganalyzer {
  186. if [ ! -f loganalyzer-${LAVERSION}.tar.gz ]; then wget http://download.adiscon.com/loganalyzer/loganalyzer-${LAVERSION}.tar.gz; fi
  187. tar -xf loganalyzer-${LAVERSION}.tar.gz
  188. cp -r loganalyzer-${LAVERSION}/src/* /var/www/html/
  189. rm -rf ./loganalyzer-${LAVERSION}/
  190. rm /var/www/html/index.html
  191. chown www-data:www-data -R /var/www/html/
  192. #create new db for loganalyzer user and settings with rsyslog user as admin
  193. mysql -uroot -e "create database loganalyzer; grant all privileges on loganalyzer.* to rsyslog@localhost"
  194. #disable new version check during logon into admin panel
  195. r="\$content\['UPDATEURL'\] = \"http://loganalyzer.adiscon.com/files/version.txt\";"
  196. sed -i  "s|${r}|\$content\['UPDATEURL'\] = \"\";|g"  /var/www/html/include/functions_common.php
  197. }
  198. ####
  199. ## MAIN
  200. ####
  201. instaldebconf="false"
  202. installmariadb="false"
  203. installapache="false"
  204. installphp="false"
  205. instalphpmyadmin="false"
  206. installrsyslog="false"
  207. printf "Checking debconf-utils..."
  208. check=$( check_packages debconf-utils )
  209. if [[ $check != "true" ]]; then
  210.     echo "......will be installed"
  211.     instaldebconf="true"
  212. else
  213.     echo "......found!"
  214. fi
  215. printf "Checking sql..."
  216. check=$( check_sql )
  217. case ${check} in
  218.     false)
  219.         echo "................will be installed: MariaDB"
  220.         installmariadb="true"
  221.         ;;
  222.     mysql)
  223.         echo "................MySQL found, it will be used during installation"
  224.         ;;
  225.     mariadb)
  226.         echo "................MariaDB found, it will be used during installation"
  227.         ;;
  228. esac
  229. printf "Checking apache..."
  230. check=$( check_packages apache2 )
  231. if [[ $check != "true" ]]; then
  232.     echo ".............will be installed"
  233.     installapache="true"
  234. else
  235.     echo ".............found!"
  236. fi
  237. printf "Checking php..."
  238. check=$( check_php )
  239. if [[ $check != "true" ]]; then
  240.     echo "................will be installed: ${check}"
  241.     installphp="true"
  242.     phptoinstall=${check}
  243. else
  244.     echo "................found!"
  245. fi
  246. printf "Checking rsyslog-mysql..."
  247. check=$( check_packages rsyslog-mysql )
  248. if [[ $check != "true" ]]; then
  249.     echo "......will be installed"
  250.     installrsyslog="true"
  251. else
  252.     echo "......found!"
  253. fi
  254. printf "Checking phpmyadmin..."
  255. check=$( check_phpmyadmin )
  256. if [[ $check == "false" ]]; then
  257.     echo ".........not found in /usr/share/phpmyadmin/"
  258.     echo
  259.     while true; do
  260.         read -p "Do you wish to install phpmyadmin? (y/n): " yn
  261.         case $yn in
  262.             [Yy] ) instalphpmyadmin="true"; break;;
  263.             [Nn] ) break;;
  264.             * ) echo "Please answer [y]es or [n]o.";;
  265.         esac
  266.     done
  267. else
  268.     echo ".........found!"
  269. fi
  270. echo
  271. echo "This file(s) should be downloaded:"
  272. if [[ ${instalphpmyadmin} != "false" ]]; then echo "https://files.phpmyadmin.net/phpMyAdmin/${PMAVER}/phpMyAdmin-${PMAVER}-all-languages.tar.gz" ; fi
  273. echo "http://download.adiscon.com/loganalyzer/loganalyzer-${LAVERSION}.tar.gz"
  274. echo "You can place this file(s) into current directory manually if you have not an Internet connection."
  275. echo "Debian repository must be accessible!"
  276. echo
  277. read -p "Ok, let's do it. Press ENTER to install, CTRL-C - to abort."
  278. # INSTALL
  279. apt update
  280. if [[ ${instaldebconf} != "false" ]]; then apt -y install debconf-utils ; fi
  281. if [[ ${installmariadb} != "false" ]]; then install_sql ; fi
  282. if [[ ${installapache} != "false" ]]; then apt -y install apache2 ; fi
  283. if [[ ${installphp} != "false" ]]; then  apt -y install ${phptoinstall} ; fi
  284. if [[ ${installrsyslog} != "false" ]]; then  install_rsyslog_mysql  ; fi
  285. installrsyslog
  286. if [[ ${instalphpmyadmin} != "false" ]]; then install_phpmyadmin ; fi
  287. install_loganalyzer
  288. serverip=$(ip addr show | grep -o "inet [0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | grep -o "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | grep -v "^127.0.0.1" | head -n 1)
  289. echo
  290. echo
  291. echo Installation complete
  292. echo
  293. if [[ ${instalphpmyadmin} != "false" ]]; then
  294.     echo "phpmyadmin address: http://${serverip}/phpmyadmin"
  295.     echo "             login:${PHPMYADMINUSER}"
  296.     echo "          password:${PHPMYADMINPASS}"
  297.     echo
  298. fi
  299. echo "Open IP address of this server in web-browser (http://${serverip}/)"
  300. echo and use next settings for wizard:
  301. echo
  302. echo "User Database Options (optional)"
  303. echo "Enable User Database: Yes"
  304. echo "       Database User: rsyslog"
  305. echo "   Database Password: ${SYSLOGDBPASSWORD}"
  306. echo
  307. echo "First Syslog Source"
  308. echo "                        Source Type: MYSQL Native"
  309. echo "     Database Name (case-sensitive): Syslog"
  310. echo "Database Tablename (case-sensitive): SystemEvents"
  311. echo "                      Database User: rsyslog"
  312. echo "                  Database Password: ${SYSLOGDBPASSWORD}"
  313. echo "                Enable Row Counting: No"

 

Лучше всего использовать его на свежеустановленном дебиане в минимальной конфигурации. Перед запуском нужно его отредактировать, изменив пароль для создаваемого в процессе пользователя (базы данных) rsyslog, а также логин и пароль дополнительного администратора БД, который будет создан в случае установки (опциональной) PhpMyAdmin.  Всего скрипт установит:

  1. mariadb в качестве сервера баз данных,
  2. rsyslog с коннектором rsyslog-mysql в качестве сислог сервера, и создаст конфиг для приема логов по сети
  3. веб-сервер apache
  4. опционально phpmyadmin для управления базой данных
  5. LogAnalyzer.

После завершения работы скрипта останется пройти мастера настройки LogAnalyzer, перейдя в браузере по адресу вашего сервера. Так что если вы решите его использовать, то можете листать к разделу «Настройка LogAnalyzer», а также посмотреть наглядное видео:

Настройка сервера syslog с веб-интерфейсом и хранением логов в базе данных (на Debian 10 «buster»)

Установка debian

Для наших целей подойдет установленный в минимальной конфигурации дебиан. Идеально сразу при установке поставить LAMP-набор, отметив опцию web server

Но можно обойтись пунктом standard system utilities, а все остальное установить самостоятельно. Я буду исходить из этого варианта.

Установка базы данных MariaDB

 
 
  1. apt update && apt -y install mariadb-server

Установка веб-сервера apache

 
 
  1. apt -y install apache2

Установка необходимых модулей PHP

 
 
  1. apt -y install php libapache2-mod-php php-mysql php-common php-cli php-json php-gd php-opcache php-readline php-mbstring

Установка rsyslog

 
 
  1. apt -y rsyslog-mysql

В процессе установки соглашаемся сконфигурировать базу данных

Придумываем и подтверждаем пароль для пользователя «rsyslog» БД

После того как сислог будет установлен, его нужно сконфигурировать так, чтобы он мог принимать события с сетевого оборудования. Создаем файл /etc/rsyslog.d/enable-remote.conf с таким содержанием:

/etc/rsyslog.d/enable-remote.conf
 
  1. module(load="imudp")
  2. input(type="imudp" port="514")
  3. module(load="imtcp")
  4. input(type="imtcp" port="514")

И перезагружаем сервис

 
 
  1. service rsyslog restart

Установка веб-интерфейса (LogAnalyzer)

Скачиваем и распаковываем файлы в директорию веб-сервера:

 
 
  1. wget http://download.adiscon.com/loganalyzer/loganalyzer-4.1.7.tar.gz
  2. tar -xvf loganalyzer-4.1.7.tar.gz
  3. rm loganalyzer-4.1.7.tar.gz
  4. cp -r loganalyzer-4.1.7/src/* /var/www/html/
  5. rm /var/www/html/index.html
  6. chown www-data:www-data -R /var/www/html/

Перед тем как перейти мастеру настройки, создадим еще одну одну базу, в которой лог аналайзер будет хранить свои настройки, и дадим пользователю rsyslog полные права на нее

 
 
  1. mysql -uroot -e "create database loganalyzer; grant all privileges on loganalyzer.* to rsyslog@localhost"

Настройка LogAnalyzer

Теперь к мастеру. Перейдя в браузере по адресу сервера мы должны увидеть его приглашение создать конфиг

Первые два шага пропускаем, интересное  начинается с третьего. Здесь нужно выбрать Enable User Database и ввести имя пользователя (rsyslog)  и пароль с которыми будет производиться подключение к созданной нами базе «loganalyzer». Остальные настройки можно оставить по умолчанию.

На шестом шаге создаем внутреннего администратора лог аналайзера

На следующем шаге выбираем источник данных — базу rsyslog\’а. Важные пункты, которые будет нужно здесь изменить:

  1. Source Type — MYSQL Native
  2. Database Name — Syslog (была создана при установке rsyslog-mysql)
  3. Database Tablename — SystemEvents (именно так — с большими буквами, а не как по дефолту)
  4. Database User — rsyslog
  5. Database Password — пароль юзера rsyslog
  6. Enable Row Counting — No (при объеме логов в несколько гигабайт этот подсчет приводит к более чем минутным запросам к БД)

Next, Finish и перед нами интерфейс лог аналайзера

Тюнинг

Теперь остается сделать две довольно важные вещи.

Во-первых, при попытке логина в админку, лог аналайзер пытается проверить свое собственное обновление, и это приводит к длительному ожиданию, если сервер не имеет выхода в интернет. Чтобы этого не происходило — нужно отредактировать файл /var/www/html/include/functions_common.php, изменив строку

 
 
  1. $content['UPDATEURL'] = "http://loganalyzer.adiscon.com/files/version.txt";

на

 
 
  1. $content['UPDATEURL'] = "";

Второе, и не менее важное. Со временем база сислога будет опухать, поэтому ее стоит периодически очищать от старых записей. Для этого нужно создать файлик /etc/cron.d/rsyslog_mysql с содержимым

 
 
  1. 23 2     * * *     root mysql -uroot -e 'use Syslog; DELETE FROM SystemEvents WHERE ReceivedAt < DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 365 DAY);'

С ним каждый день из базы будут удаляться записи старше 365 дней (разумеется это количество можно поправить на необходимое)

 

[из техноблога] Abaqus: Unable to validate FLEXnet server

На днях с коллегой  столкнулись с проблемой при установке abaqus, на которую потратили бессовестно много времени. Ошибка возникала при указании сервера лицензий (FlexLM) и в сокращенном виде звучала как «Unable to validate FLEXnet server»

Далее: http://obrivki.blogspot.ru/2017/01/abaqus-unable-to-validate-flexnet-server.html

Новый прорыв в гугле

Сегодня пришло письмо, в котором гугл радостно сообщил о новой вехе в истории развития их переводческого сервиса. На смену классическим алгоритмам перевода пришли долгожданные и модные нынче нейросети, на которые, в общем-то вполне заслуженно, возлагаются надежды в различнейших областях. Вот что пишет гугл:

Недавно в сервисе «Google Переводчик» произошли изменения более важные, чем за предыдущие десять лет. И Вы тоже внесли свой вклад!
Google Переводчик начал использовать нейронные сети для 41 языка, включая русский. Нейронные сети постоянно обучаются и самосовершенствуются.

Ну что ж, очевидно что самосовершенствующаяся система это круто, она неизбежно приведет нас к миру терминатора, но было бы забавно оценить прогресс этой системы по части перевода неких рандомных текстов. Так что прямо сейчас я воспользуюсь старым-добрым трюком — загоню этот самый текст, что я написал, в гугл транслейт. Сделаю перевод на 3 языка, а потом обратно. Посмотрим что из этого получится, а потом, через годик, повторю этот эксперимент — даже напоминание себе в календаре сделаю =). В качестве подопытных языков использую английский — с ним проблем должно быть меньше всего — французский и какой-нибудь менее распространенный итальянский. Итак, погнали!

Английский

Сегодня пришло письмо, в котором гугл радостно сообщил о новой вехе в истории развития их переводческого сервиса. На смену классическим алгоритмам перевода пришли долгожданные и модные нынче нейросети, на которые, в общем-то вполне заслуженно, возлагаются надежды в различнейших областях. Вот что пишет гугл:

«Недавно в сервисе «Google Переводчик» произошли изменения более важные, чем за предыдущие десять лет. И Вы тоже внесли свой вклад!
Google Переводчик начал использовать нейронные сети для 41 языка, включая русский. Нейронные сети постоянно обучаются и самосовершенствуются.»

Ну что ж, очевидно что самосовершенствующаяся система это круто, она неизбежно приведет нас к миру терминатора, но было бы забавно оценить прогресс этой системы по части перевода неких рандомных текстов. Так что прямо сейчас я воспользуюсь старым-добрым трюком — загоню этот самый текст, что я написал, в гугл транслейт. Сделаю перевод на 3 языка, а потом обратно. Посмотрим что из этого получится, а потом, через годик, повторю этот эксперимент — даже напоминание себе в календаре сделаю =). В качестве подопытных языков использую английский — с ним проблем должно быть меньше всего — французский и какой-нибудь менее распространенный итальянский. Итак, погнали!

Today we received a letter in which Google happily reported a new milestone in the history of the development of their translation service. The classical algorithms of translation were replaced by long-awaited and fashionable neural networks, which, in general, deservedly, are placed in different areas. Here’s what Google writes:

«Recently, the Google Translate service has changed more important than the previous ten years, and you also contributed!
Google Translator began using neural networks for 41 languages, including Russian. Neural networks are constantly being trained and cultivated. «

Well, it’s obvious that the self-improving system is cool, it will inevitably lead us to the world of the terminator, but it would be fun to evaluate the progress of this system with regard to the transfer of some random texts. So right now I’ll use the old-good trick — I will drive this very text that I wrote to google translate. I will make a translation into 3 languages, and then back. Let’s see what happens from this, and then, in a year, I will repeat this experiment — I will even make a reminder to myself in the calendar =). As test languages ​​I use English — with him problems should be least of all — French and some less common Italian. So, drive!

Сегодня мы получили письмо, в котором компания Google с радостью сообщила о новой вехе в истории развития своих услуг перевода. Классические алгоритмы перевода были заменены долгожданными и модными нейронными сетями, которые, в общем, заслуженно, размещены в разных областях. Вот что пишет Google:

«В последнее время служба Google Translate изменилась важнее, чем предыдущие десять лет, и вы также внесли свой вклад!
Google Translator начал использовать нейронные сети для 41 языка, включая русский. Нейронные сети постоянно обучаются и культивируются. «

Ну, очевидно, что самовосстанавливающаяся система — это круто, она неизбежно приведет нас к миру терминатора, но было бы интересно оценить прогресс этой системы в отношении передачи некоторых случайных текстов. Поэтому прямо сейчас я буду использовать старый добрый трюк — я буду управлять этим самым текстом, который я написал для перевода Google. Я сделаю перевод на 3 языка, а затем обратно. Посмотрим, что из этого получится, и тогда, через год, я повторю этот эксперимент — я даже сделаю себе напоминание в календаре =). В качестве тестовых языков я использую английский — с ним проблемы должны быть наименее всего — французский и немного менее распространенный итальянский. Итак, езжай!

Французский

Сегодня пришло письмо, в котором гугл радостно сообщил о новой вехе в истории развития их переводческого сервиса. На смену классическим алгоритмам перевода пришли долгожданные и модные нынче нейросети, на которые, в общем-то вполне заслуженно, возлагаются надежды в различнейших областях. Вот что пишет гугл:

«Недавно в сервисе «Google Переводчик» произошли изменения более важные, чем за предыдущие десять лет. И Вы тоже внесли свой вклад!
Google Переводчик начал использовать нейронные сети для 41 языка, включая русский. Нейронные сети постоянно обучаются и самосовершенствуются.»

Ну что ж, очевидно что самосовершенствующаяся система это круто, она неизбежно приведет нас к миру терминатора, но было бы забавно оценить прогресс этой системы по части перевода неких рандомных текстов. Так что прямо сейчас я воспользуюсь старым-добрым трюком — загоню этот самый текст, что я написал, в гугл транслейт. Сделаю перевод на 3 языка, а потом обратно. Посмотрим что из этого получится, а потом, через годик, повторю этот эксперимент — даже напоминание себе в календаре сделаю =). В качестве подопытных языков использую английский — с ним проблем должно быть меньше всего — французский и какой-нибудь менее распространенный итальянский. Итак, погнали!

Aujourd’hui, a reçu une lettre dans laquelle Google a annoncé heureusement une nouvelle étape dans l’histoire de leur service de traduction. A la place des algorithmes classiques de traduction est venu le tant attendu et à la mode réseau maintenant de neurones, qui, en général, tout à fait à juste titre, espérés dans divers domaines. Voici ce que Google dit:

« Récemment, quand » Google Translate « a changé plus important que dans les dix années précédentes. Et vous aussi, ont contribué!
Google Translate a commencé à utiliser des réseaux de neurones à 41 langues, dont le russe. Les réseaux de neurones sont constamment formés et cultiver ».

Eh bien, il est évident que le système de cultivateur est cool, il nous conduira inévitablement au monde de Terminator, mais il serait amusant d’évaluer les progrès de ce système de la part d’une traduction au hasard de certains textes. Alors maintenant, j’utiliser un bon vieux truc — le paddock c’est le texte que je l’ai écrit dans sur Google Translate. J’ai fait une traduction en 3 langues, puis de nouveau. Voyons voir ce qui se passe, puis, en un an, répéter l’expérience — même un rappel pour vous de faire le calendrier =). Comme une langue expérimentale est l’anglais — des problèmes qu’elle doit être inférieure à tous — les Français et d’autres moins commune italienne. Nous avons donc roulé!

Сегодня получил письмо от Google с радостью объявил новый этап в истории их службы перевода. Вместо алгоритмов классического перевода пришел долгожданный и теперь сетевой режим нейронов, которые, в общем, совершенно справедливо ожидать в различных областях. Вот что говорит Google:

«В последнее время, когда» Google Translate «изменился больше, чем в предыдущие десять лет. И вы тоже внесли свой вклад!
Google Переводчик начал с использованием нейронных сетей на 41 языках, включая русский. Нейронные сети постоянно обучаются и растут «.

Ну, очевидно, что система фермера круто, это неизбежно приведет нас к миру Терминатора, но было бы интересно оценить прогресс этой системы от случайного перевода некоторых текстов. Так что теперь я использую старый трюк — загон это текст, который я написал в на Google Translate. Я сделал перевод на 3-х языках, а затем обратно. Давайте посмотрим, что происходит, то в течение года, повторить опыт — даже напоминание планировать =). Как экспериментальный язык английский язык — проблемы, он должен быть меньше, чем все — французы и другие, менее распространенный итальянский. Таким образом, мы поехали!

Итальянский

Сегодня пришло письмо, в котором гугл радостно сообщил о новой вехе в истории развития их переводческого сервиса. На смену классическим алгоритмам перевода пришли долгожданные и модные нынче нейросети, на которые, в общем-то вполне заслуженно, возлагаются надежды в различнейших областях. Вот что пишет гугл:

«Недавно в сервисе «Google Переводчик» произошли изменения более важные, чем за предыдущие десять лет. И Вы тоже внесли свой вклад!
Google Переводчик начал использовать нейронные сети для 41 языка, включая русский. Нейронные сети постоянно обучаются и самосовершенствуются.»

Ну что ж, очевидно что самосовершенствующаяся система это круто, она неизбежно приведет нас к миру терминатора, но было бы забавно оценить прогресс этой системы по части перевода неких рандомных текстов. Так что прямо сейчас я воспользуюсь старым-добрым трюком — загоню этот самый текст, что я написал, в гугл транслейт. Сделаю перевод на 3 языка, а потом обратно. Посмотрим что из этого получится, а потом, через годик, повторю этот эксперимент — даже напоминание себе в календаре сделаю =). В качестве подопытных языков использую английский — с ним проблем должно быть меньше всего — французский и какой-нибудь менее распространенный итальянский. Итак, погнали!

Oggi ha ricevuto una lettera in cui Google felicemente annunciato una nuova pietra miliare nella storia del loro servizio di traduzione. Al posto dei classici algoritmi di traduzione è arrivata la rete neurale ora tanto attesa e alla moda, che, in genere, abbastanza meritatamente, l’auspicata in vari campi. Ecco cosa dice Google:

«Recentemente, quando» Google Translate «è cambiato più importante che negli ultimi dieci anni. E anche voi, hanno contribuito!
Google Translate iniziato a utilizzare le reti neurali per 41 lingue, compreso il russo. Le reti neurali sono costantemente addestrati e coltivare. «

Beh, è ​​ovvio che il sistema coltivatore è fresco, ma ci porterà inevitabilmente al mondo di Terminator, ma che sarebbe stato divertente per valutare lo stato di avanzamento di questo sistema da parte di un caso di traduzione di alcuni testi. Così ora io uso buon vecchio trucco — paddock questo è il testo che ho scritto in su Google Translate. Ho fatto una traduzione in 3 lingue e poi di nuovo indietro. Vediamo cosa succede, e poi, in un anno, ripetere l’esperimento — anche un promemoria per se stessi per rendere il calendario =). Come un linguaggio sperimentale è l’inglese — dai problemi che deve essere inferiore a tutti — il francese e un po ‘meno comuni italiani. Così siamo arrivati!

Сегодня он получил письмо от Google с радостью объявила о новой вехой в истории их службы перевода. Вместо классических алгоритмов перевода пришел нейронную сеть в настоящее время долгожданная и моды, которые, в общем, вполне заслуженно, желаемой в различных областях. Вот что говорит Google:

«В последнее время, когда» Google Translate «изменился более важным, чем в последнее десятилетие. И вы тоже внесли свой вклад!
Google Переводчик начал с использованием нейронных сетей на 41 языках, включая русский. Нейронные сети постоянно обучаются и культивируют. «

Ну, очевидно, что система фермер свеж, но неизбежно приведет нас к миру Терминатора, но было бы интересно оценить прогресс этой системы в случае какого-либо перевода текстов. Так что теперь я использую старый добрый трюк — загон это текст, который я написал, чтобы Google Translate. Я сделал перевод на 3-х языках, а затем обратно. Давайте посмотрим, что происходит, а потом, через год, повторить эксперимент — также напоминание о себе, чтобы сделать календарь =). В качестве экспериментального языка английский — от проблем, которые должны быть меньше, чем все — французский и немного «менее распространенный итальянский. Таким образом, мы пришли!

Посмотрим что будет через год =)

Debian 10 Buster + openbox (установка и настройка)

Уже давно сижу на облегченном дебиане, собранном с минимальной инсталляции. В процессе эксплуатации набралось шпаргалок по сборке системы моей мечты, которые записывались в файлик, а теперь перенесены в чуть более развернутом виде сюда на сайт.

Читать: Debian 10 Buster + openbox (установка и настройка)

Общая сетевая папка NFS+SAMBA с правами для всех

Цель: расшарить директорию через NFS и CIFS так, чтобы для обоих протоколов был общий доступ и файлы, залитые через один протокол, были доступны на чтение и запись через второй.

Метод, до которого дошел я (возможно не единственный), реализуется через дополнительного пользователя на сервере, с которого шарится директория. В моем случае это пользователь «user» с UID 1000 (входит в одноименную группу с GID 1000).

Файл /etc/exports выглядит так

/etc/exports
 
  1. "/share/Public" *(rw,nohide,async,no_subtree_check,crossmnt,all_squash,anonuid=1000,anongid=1000)

ключевыми параметрами являются:

  1. all_squash — опция говорит что все подключения будут считаться анонимными
  2. anonuid=1000 — определяет UID пользователя от имени которого будет осуществляться доступ для анонимных подключений
  3. anongid=1000 — аналогично определяет GID

Таким образом все файлы и папки, создаваемые на сервере через NFS будут иметь владельца «user»

 
 
  1. ls -la /share/Public/test_dir/
 
 
  1. total 12
  2. drwxr-xr-x  3 user user 4096 Jan 14 16:18  .
  3. drwxrwxrwx 14 user user 4096 Jan 14 16:18  ..
  4. drwxr-xr-x  2 user user 4096 Jan 14 16:18 'nfs dir'
  5. -rw-r--r--  1 user user    0 Jun 19  2020 'nfs file.txt'

Теперь можно переходить к настройке самбы. Приведу весь свой конфиг /etc/samba/smb.conf

/etc/samba/smb.conf
 
  1. [global]
  2.     workgroup = WORKGROUP
  3.     dns proxy = no
  4.     log file = /var/log/samba/log.%m
  5.     max log size = 200
  6.     panic action = /usr/share/samba/panic-action %d
  7.     server role = standalone server
  8.     passdb backend = tdbsam
  9.     obey pam restrictions = yes
  10.     unix password sync = yes
  11.     passwd program = /usr/bin/passwd %u
  12.     passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
  13.     pam password change = yes
  14.     map to guest = bad user
  15.     security = user
  16.     usershare allow guests = yes
  17.     acl allow execute always = true
  18.     allow insecure wide links = yes
  19.     # WinXP/Android X-Plore compatibility:
  20.     ntlm auth = yes
  21.     server min protocol = NT1
  22. [Public]
  23.     path = /share/Public
  24.     mangled names = no
  25.     writeable = yes
  26.     follow symlinks = yes
  27.     wide links = yes
  28.     browsable = yes
  29.     guest ok = yes
  30.     guest only = yes
  31.     force user = user
  32.     force group = user
  33.     create mask = 666
  34.     directory mask = 777
  35.     #recycle
  36.     vfs objects = recycle
  37.     recycle:repository = /share/Public/.Trash-1000/files/%U
  38.     recycle:keeptree = Yes
  39.     recycle:versions = Yes
  40.     recycle:touch = Yes

Здесь ключевыми являются опции директории:

  1. guest ok = yes — пускать юзера без аутентификации
  2. guest only = yes — пускать только анонимусов
  3. force user = user — работа с файлами ведется от имени указанного юзера «user» (аналог nfs-ного «anonuid=1000»)
  4. force group = user — аналогично для группы (аналог nfs-ного «anongid=1000»)

Корзина

Отдельно прокомментирую вот это кусок

 
 
  1. #recycle
  2.     vfs objects = recycle
  3.     recycle:repository = /share/Public/.Trash-1000/files/%U
  4.     recycle:keeptree = Yes
  5.     recycle:versions = Yes
  6.     recycle:touch = Yes

Дело в том, что при удалении файлов из директории, смонтированной через NFS, скорее всего (это зависит от файлового менеджера)  в точке монтирования будет создана директория корзины «.Trash-1000». В то же время клиенты Windows для сетевых путей корзину не используют, но именно для этого существуют опции самбы, которые включают сетевую корзину и позволяют указать — в какой именно директории хранить удаляемые файлы. В моем случае опция «recycle:repository» указывает правило хранения, которое соответствует правилам удалению в корзину в линуксе. То есть, при такой настройке, файлы что удаленные в линуксе через NFS, что в виндусе через SMB — попадут в один каталог. В винде, конечно, они не появятся в виндовой «корзине», но как минимум их можно будет достать из папки «.Trash-1000».

Однако при этом возникает парадоксальная ситуация, что в винде данную «корзину» очистить не получится, так как она лежит в той же шаре. То есть удаляемые файлы будут валиться в нее же. Воркэраундом является создание дополнительной отдельной шары под эту директорию, чтобы через нее удалять ненужные файлы.

/etc/samba/smb.conf
 
  1. [Trash]
  2.     path = /share/Public/.Trash-1000
  3.     mangled names = no
  4.     writeable = yes
  5.     follow symlinks = yes
  6.     wide links = yes
  7.     browsable = yes
  8.     guest ok = yes
  9.     guest only = yes
  10.     force user = user
  11.     force group = user
  12.     create mask = 666
  13.     directory mask = 777

Мой удобренный хром

Уже много лет прошло с тех пор, как я пересел с файрфокса на хром, отказавшись тогда от многих удобных расширений. Тем не менее и хром оброс у меня ими, и сейчас, когда большинство расширений стоит у меня уже не один год, я хочу их перечислить. Почти всеми я пользуюсь регулярно и не думаю менять на аналоги, так как все они представляются мне достаточно удобными. Название кликабельны.

AdBlock

Особо нечего рассказывать, это расширение прекрасно известно. Все кто любит интернет без навязчивой рекламы — ставят его. Почему не AdBlockPlus? А не помню, кажется плюс у меня то ли падал, то ли что-то еще. В общем я поставил обычный и он прекрасно справляется с обязанностями.

BugMeNot Lite

Это, наверное, самое редко используемое расширение является оберткой к сервису BugMeNot. Суть сервиса — силами пользователей собирать публичные учетки к различного рода форумам, сайтам и т.д. и т.п. Не секрет, что иногда на форумах без регистрации нельзя скачать файл, или посмотреть информацию за тегом спойлера, тут и может выручить багминот, предоставив рабочую учетку. Далеко не всегда учетки оказываются живыми, но несколько раз расширение выручало и стоит на всякий случай.

Checker Plus for Gmail™

Кнопочка-оповещение о непрочитанной почте, позволяет читать и удалять письма не заходя в основной интерфейс сервиса.

Checker Plus for Google Calendar™

От автора предыдущего расширения. Оповещалка о событиях в календаре, дополнительно дает смотреть список записей и доставлять новые не открывая основной интерфейс сервиса.

Disable Youtube™ HTML5 Player

В какой-то момент у меня начал глючить HTML5-проигрыватель на ютубе, данное же расширение принудительно запускает флешовый.

Flying Focus

Специфическое «свистопердельное» расширение, добавляющее анимацию при переходе между контролами путем нажатия кнопки TAB. Бывает удобно при заполнении больших форм с кучей полей, когда поля выбираются табом не в логичном порядке.

FoxyProxy Standard

Расширение позволяет автоматом выбирать прокси-сервера на основе URL’ов, можно использовать маски.

goo.gl URL Shortener

Сокращалка длинных адресов для удобной вставки оных в чаты, аськи и т.д. и т.п.

Google Play Музыка

Расширение для пользователей одноименного сервиса.

Humble New Tab Page

Одно из недавно установленных расширений. Меняет отображение новой вкладки, делая его более лаконичным и функциональным.

LinguaLeo

Расширение от одноименного сервиса, работает только при наличии регистрации на нем. Примечательно тем, что дает возможность тыкнуть в иностранное слово на любой странице и получить список его переводов, то есть использовать себя в качестве словаря.

MS KB English Only

Принудительный показ статей базы знаний Microsoft на английском языке. В противном случае показываются переведенные «промтом» русские варианты, так что настоящий мастхейв для админов.

pageUp

Добавляет в левый-верхний угол каждой страницы кнопочку по которой страница скроллится в самый верх. Расширение не всегда корректно работает, но тем не менее бывает весьма полезным.

Генератор паролей работающий на основе общего мастер-пароля и имени домена. Позволяет генерировать уникальные пароли под каждый сайт и вставлять их при необходимости.

Search by Image (by Google)

Поиск картинки через гугл, удобно для поиска картинки в высоком разрешении.

TinEye Reverse Image Search

Расширение от пионера технологии поиска картинок по образцу. С момента появления аналогичного сервиса от гугла пользуюсь все реже, но иногда этот сервис находит то, что не нашел гугл, поэтому расширение все еще полезно и установлено.

Tiny Tiny RSS Notifier

А вот свою RSS-читалку гугл прикрыл, что заставило меня развернуть свою на удаленном сервере, данное же расширение, как понятно из названия, оповещает меня об обновлениях подписок.

Transmission easy client

Великолепное расширение, позволяющее работать с удаленным торрент-клиентом transmission, который установлен у меня дома на сетевой хранилке. Позволяет добавлять и удалять торренты, тормозить и запускать, включать ограничение скорости.

TrashMail.net for Google Chrome™

Расширение от одноименного сервиса, позволяющего создать временный почтовый ящик с переадресацией на настоящий. Удобно когда нужно зарегаться на каком-нибудь сомнительном сервисе от которого можно ожидать поток спама.

VkOpt

Неплохой кастомизатор интерфейса вконтакта.

ZenMate

Прокси-сервис с серверами в Гонконге, UK, US, Швейцарии и Германии. Требует регистрации.

Оповещения Google+
Комментарии не требуются

Продвинутая история

Изменение страницы истории в браузере. Удобное представление под дням, девайсам, поиск, теги.

Редактирование и отправка скриншотов

Тулза для скриншотирования страниц с возможностью добавить стрелочки, обводки и т.п., и выложить на публичные сервисы.
И на этом всё =) Большая часть расширений стоит у меня уже больше года, какие-то были добавлены недавно вместо аналогичных, но показавшихся мне менее удобными или более глючными.

Централизованное хранилище VMware Tools и тулзы для Windows 2003 на vSphere 6.7

Обновив сферу, столкнулся с ошибкой:

Unable to install VMware Tools. An error occurred while trying to access image file «/usr/lib/vmware/isoimages/winPreVista.iso» needed to install VMware Tools: 2 (No such file or directory). Please refer the product documentation or KB article 2129825 for details about how to get VMware Tools package for this guest operating system

Также ошибка может звучать более лаконично:

The required VMware Tools ISO image does not exist or is inaccessible.

Оказывается, в составе новых ESXI нет исошника с тулзами под старые системы, хотя некоторая обратная совместимость, в виде «знания» какой исошник нужно монтировать — осталась.

Решение этой проблемы — скачать legacy-tools и добавить нужные исошники. Но так как это придется делать для каждого хоста, то проще на общедоступном сторадже выделить папку, свалить туда все исошники и поправить на каждом из серверов параметр, указывающий на папку с тулзами.

Собственно, тулзы качаются с сайта вмвари: легаси и текущие на данный момент.

Оба архива содержат папки «floppies» и «vmtools» — совмещаем их содержимое (для легаси и новых тулзов содержимое не пересекается) и аплоадим эти две папки в отдельную папку в хранилище. Допустим, в папку vmwaretools на сторадже myStorage (таким образом у нас две папки: «myStorage/vmwaretools/floppies» и «myStorage/vmwaretools/vmtools»)

Теперь заходим в «Advanced System Settings» одного из серверов:

Ищем в них параметр «UserVars.ProductLockerLocation«, по дефолту его значение — «/locker/packages/vmtoolsRepo/», меняем его на «/vmfs/volumes/myStorage/vmwaretools/«.

Теперь сервер придется перезагрузить (по крайней мере у меня он не подхватил новое значение без ребута). После перезагрузки тулзы будут корректно ставиться уже из хранилища.

Технически можно повторить процедуру вручную на каждом из серверов, или включить этот параметр в host profile:

 

Ссылки по теме:

Скрипт для поднятия SOCKS-прокси посредством ssh с проверкой его работоспособности

Небольшой скрипт, которым я пользуюсь для поднятия прокси-через-ssh. Висит в автозагрузке, постоянно проверяет при помощи курла доступность гугла, в случае недоступности — прибивает нужное ssh-соединение и открывает его снова.

ЗЫ: свой собственный прокси с шифрованием трафика средствами ssh — рекомендации лучших собаководов )

proxy.sh
 
  1. #!/bin/sh
  2. # establishes an SSH Socks proxy and reconnects if it fails.
  3. socksPort=8376
  4. server=example.com
  5. user=myproxyuser
  6. key=~/.ssh/id_rsa_myproxyuser
  7. while true
  8. do
  9.     timeout 20 curl --retry-max-time 1 --retry 5 --retry-delay 1 -x socks5://127.0.0.1:${socksPort} http://google.com/ > /dev/null 2>&1
  10.     if [ $? -ne 0 ]
  11.     then
  12.         echo $(date) reconnect...
  13.         while ps -eo pid,cmd | grep ssh | grep ${socksPort}
  14.         do
  15.             kill $(ps -eo pid,cmd | grep ssh | grep ${socksPort} | awk '{print $1}' | head -n 1)
  16.         done;
  17.         ssh -D ${socksPort} -f  -q -N -i "${key}" ${user}@${server}
  18.     else
  19.         sleep 10
  20.     fi
  21. done;

Вариант для cygwin

proxy_cygwin.sh
 
  1. #!/bin/sh
  2. # establishes an SSH Socks proxy and reconnects if it fails.
  3. socksPort=8376
  4. server=example.com
  5. user=myproxyuser
  6. key=~/.ssh/id_rsa_myproxyuser
  7. while true
  8. do
  9.     timeout 20  curl -x socks5://127.0.0.1:${socksPort} http://google.com/  > /dev/null 2>&1
  10.     if [ $? -ne 0 ]
  11.     then
  12.         while ps -e | grep ssh;
  13.         do
  14.             # /bin/kill - is important!
  15.             /bin/kill -f $(grep -a "ssh" /proc/*/cmdline  | grep -a  ${socksPort} | awk -F '/' '{print $3}' | head -n 1)
  16.         done;
  17.         echo $(date) reconnect
  18.         ssh -D ${socksPort} -fNq -i "${key}" ${user}@${server}
  19.     else
  20.         sleep 10
  21.     fi
  22. done;