{"id":287552,"date":"2018-08-16T14:11:50","date_gmt":"2018-08-16T10:11:50","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=287552"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=287552","title":{"rendered":"\u041e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e\u0433\u043e \u0434\u0430\u0439\u0434\u0436\u0435\u0441\u0442\u0430 \u043b\u043e\u0433\u043e\u0432 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e python \u0438 ansible \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 asterisk"},"content":{"rendered":"\n<div data-io-article-url=\"https:\/\/habr.com\/post\/419547\/\" class=\"post__text post__text-html js-mediator-article\">\u041a\u043e\u0433\u0434\u0430 \u044f \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u043b \u0434\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442, \u044f \u043d\u0435 \u0431\u044b\u043b \u0437\u043d\u0430\u043a\u043e\u043c \u0441 logwatch. \u041c\u043d\u0435 \u0437\u0430\u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0432\u0438\u0434\u0435\u0442\u044c \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044e \u0441 \u043b\u043e\u0433\u0430\u043c\u0438 \u043d\u0430 \u0441\u0432\u043e\u0438\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u0430\u0445 \u0432 \u0446\u0435\u043b\u043e\u043c, \u0438, \u0442\u0430\u043a \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u044d\u0442\u043e\u0442 \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434. \u0414\u0443\u043c\u0430\u044e, \u0447\u0442\u043e \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u043d\u043e\u0432\u0438\u0447\u043a\u0430\u043c \u0432 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u0438 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439 ansible.<\/p>\n<p>  \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0435 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u044b:<\/p>\n<ul>\n<li>python 2.7.14<\/li>\n<li>ansible 2.3<\/li>\n<li>\u0441\u0435\u0440\u0432\u0435\u0440\u0430 asterisk \u043d\u0430 \u0431\u0430\u0437\u0435 FreePBX 13<\/li>\n<\/ul>\n<p>  \u041c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0434\u0432\u0443\u0445 \u0447\u0430\u0441\u0442\u0435\u0439 \u2013 python \u0441\u043a\u0440\u0438\u043f\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u043b\u043e\u0433 \u0444\u0430\u0439\u043b\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439 \u043e\u0442\u0447\u0435\u0442\u0430 \u043d\u0430 \u043f\u043e\u0447\u0442\u0443, \u0438 \u043f\u043b\u0435\u0439\u0431\u0443\u043a\u0430 \u0434\u043b\u044f \u0441\u0431\u043e\u0440\u0430 \u043b\u043e\u0433\u043e\u0432 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0438\u0445 \u0441\u043a\u0440\u0438\u043f\u0442\u0443 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438.<br \/>  <a name=\"habracut\"><\/a><br \/>  \u0421\u0430\u043c \u043f\u043b\u0435\u0439\u0431\u0443\u043a:<\/p>\n<pre><code>--- - name: parseastlogs   hosts: production_asterisks   vars:     date: \"{{ lookup('pipe', 'date +%Y%m%d') }}\"     ipaddr: \"{{ ansible_default_ipv4.address }}\"   tasks:    - debug: var=date    - debug: var=ipaddr    - fetch:\t        src: \/var\/log\/asterisk\/full-{{ date }}       dest: \/tmp\/full-{{ date }}-{{ ipaddr }}       flat: yes     - local_action: \"shell \/etc\/ansible\/localscripts\/astReporter.py {{ ipaddr }} full-{{ date }}-{{ ipaddr }}\"  \u0412 \u043f\u043b\u0435\u0439\u0431\u0443\u043a\u0435 \u0437\u0430\u0434\u0430\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0437\u0430 \u0438\u043c\u044f \u0444\u0430\u0439\u043b\u0430 \u0438 \u0437\u0430\u0431\u0438\u0440\u0430\u0435\u043c \u043b\u043e\u0433 \u0444\u0430\u0439\u043b\u044b \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043c\u043e\u0434\u0443\u043b\u0435\u043c fetch. \u0422\u0430\u043a \u043a\u0430\u043a \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0443\u0442 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b - \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 flat, \u0447\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0434\u043b\u0438\u043d\u043d\u044b\u0445 \u043f\u0443\u0442\u0435\u0439 \u043a \u043b\u043e\u0433 \u0444\u0430\u0439\u043b\u0430\u043c.  <\/code><\/pre>\n<p>  \u0421\u043a\u0440\u0438\u043f\u0442:<\/p>\n<pre><code class=\"python\">#!\/usr\/bin\/python2.7 import re from collections import Counter import yagmail import sys import datetime import os  servername=sys.argv[1] yag=yagmail.SMTP(user='ansible@2.1',password=None,host='192.168.2.1',port='25',smtp_starttls=False,smtp_set_debuglevel=0,smtp_skip_login=True) recipients=['user@mydomain.local']   filename=sys.argv[2] workdir='\/tmp\/' log_file=open(workdir+filename,'r')#sys.argv[1] loglist=list() verbosity=['NOTICE','ERROR','WARNING'] regexes = [\"Call from '.*' .* to extension '.*' rejected because extension not found in context '.*'.\", \"Identifier \\d+, identifier_type \\d+ not found in identifier list\", \"Invalid result identifier \\d+ passed in aMYSQL_clear\", \"This function can only be used on SIP channels.\", \"fwrite() returned error: Broken pipe\", \"CDR requires a value \\(CDR\\(variable\\)=value\\)\", \"Received SIP subscribe for peer without mailbox: .*\", \"Removed interface '.*' from queue '.*'\", \"Peer '.*' is trying to register, but not configured as host=dynamic\", \"Registration from '.*' failed for '.*' - Peer is not supposed to register\", \"Unable to join queue '.*'\", \"Attempt to pause interface Local\/@from-queue\/n, not found\", \"PRESENCE_STATE unknown\", \"EXTENSION_STATE requires an extension\", \"Prodding channel '.*' failed\", \"Channel '.*' sent to invalid extension but no invalid handler: context,exten,priority=.*\", \"Can't send 10 type frames with PJSIP\", \"Attempt to pause interface .+, not found\", \"Attempt to unpause interface .+, not found\", \"no samples for ulawtolin\", \"Could not find matching INVITE transaction for CANCEL request\", \"Peer '.*' is now Reachable. \\(.*\\)\", \"Peer '.*' is now UNREACHABLE!  Last qualify: .*\", \"Registration from .* failed for '.*' - Wrong password\", \"Retransmission timeout reached on transmission .*\", \"no samples for alawtolin\", \"Peer '.*' is now Lagged. \\(\\d+ms \/ \\d+ms\\)\", \"Call completed to .*\", \"Invalid retrytime at line \\d+ of .*\", \"Not accepting call completion offers from call-forward recipient .*\", \"No such context '.*' for macro '.*'\\. Was called by .*\", \"[pP]ickup .* attempt by .*\", \"Call failed to go through, reason \\(5\\) Remote end is Busy\", \"Deprecated syntax found\\..* \", \"No digits dialed for atxfer.\", \"Unable to create channel of type 'SIP' \\(cause \\d+ - Subscriber absent\\)\", \"'tls' is not a valid transport type when tlsenable=no\\. If no other is specified, the defaults from general will be used\\.\", \"'tcp' is not a valid transport type when tcpenable=no\\. If no other is specified, the defaults from general will be used\\.\", \"Queued call to .* expired without completion after \\d+ attempts\", \"Re-invite to non-existing call leg on other UA. SIP dialog .*\\. Giving up.\", \"Channel .* not found!  Variable 'BLKVM' not set to .*\\.\", \"Remote host can't match request CANCEL to call .*\\. Giving up\\.\", \"Unable to execute query \\[.*\\]\", \"SQL Exec Direct failed \\(-1\\)!\\[\", \"SQL Execute returned an error .*\", \"   -- Re-registration for .*\", \"Outbound Registration\\: Expiry for .* is \\d+ sec \\(Scheduling reregistration in \\d+ s\\)\", \"Correct auth, but based on stale nonce received from '.*'\", \"Unable to write frametype: 2\", \"Received response: \\\"Forbidden\\\" from '\\\".*\\\" .*'\", \"Huh\\?  Not an RDNIS SIP header .*\", \"Hanging up call .* - no reply to our critical packet .*\", \"Cancelling retransmit of OPTIONs \\(call id .*\\)  \", \"Still have a QUALIFY dialog active, deleting\", \"The use of '_\\.' for an extension is strongly discouraged and can have unexpected behavior.  Please use '_X\\.' instead at line .*\", \"Context '.*' tries to include nonexistent context '(.*)'\", \"aMYSQL_query: mysql_query failed\\. Error: Duplicate entry .*\", \"RTCP SR transmission error to .*, rtcp halted Operation not permitted\", \"Failed to write frame to '.*': Resource temporarily unavailable\", \"Unable to forward frametype: 2\", \"Timeout on .* on non-critical invite transaction.\", \"Unexpected control subclass '\\d+'\", \"Context '.*' for macro '.*' lacks .*\", \"No response received from '.*' on registration attempt to '.*', retrying in '\\d+'\", \"Unknown RTP codec 90 received from '.*'\", \"Invalid extension '.*', but no rule 'i' or 'e' in context '.*'\", \"Added interface '.*' to queue '.*'\", \"Exceptionally long voice queue length queuing to .*\", \"Request from '.*' failed for '.*' \\(callid: .*\\) - No matching endpoint found\", \"Junk at the beginning of frame \\d+\", \"Unable to register extension at line .*\", \"Unable to register extension '.*' priority .* in '.*', already in use\", \"Unable to load config file .*\", 'Purely numeric hostname \\(\\d+\\), and not a peer--rejecting!', \"Unknown directive .* at line \\d+ .*\", \"Context '.*' already included in '.*' context on include at line \\d+ of .*\", \"No closing parenthesis found\\? '.*' at line \\d+ of .*\", \"Can't use '.*' priority on the first entry at line \\d+ of .*\", \"The .* options are deprecated. Please see UPGRADE.txt for information\", \"Can't use '.*' priority on the first entry at line \\d+ of .*\", \"Call failed to go through, reason .*\", \"Unable to open .*\", \"Playback of message .* failed\", \"File .* does not exist in any format\", \"Playback failed on .* for .*\", \"Adding .* to .*\", \"Can't move channel. One or both is dead .*\", \"Unable to complete call pickup of .*\", \"Pickup .* failed by .*\", \"No entry in voicemail config file for .*\", \"   \\-\\- Registration for '.*' timed out, trying again \\(Attempt #[0-9]+\\)\", \"Disconnecting call .* for lack of RTP activity in [0-9]+ seconds\", \"Failed to initialize .* declining image stream\", \"Can't send 10 type frames with SIP write\", \"Set requires an '=' to be a valid assignment.\", \"Timeout but no rule 't' or 'e' in context .*\", \"RTCP RR transmission error to .*, rtcp halted Operation not permitted\", \"Unable to acquire target extension for attended transfer.\", \"Unterminated comment detected beginning on line\"] notmatched=list() matchedregex=list()  combinedre=\"(\"+\")|(\".join(regexes) + \")\"  # \u0434\u0430\u043d\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u0441\u0434\u0435\u043b\u0430\u043d \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u043e \u0441\u0442\u0440\u043e\u043a\u0430\u043c\u0438 \u0438\u0437 \u043b\u043e\u0433 \u0444\u0430\u0439\u043b\u0430. \u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u0435\u043d \u0438 \u043d\u0435 \u0432\u0441\u0435 \u043f\u043e\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f. class logitem:     ldatetime=None     ltype=None     lchan=None     lsource=None     lcontent=None     def __init__(self,ldate,ltype,lch,lsrc,lcont):         self.ldatetime=ldate         self.ltype=ltype         self.lchan=lch         self.lsource=lsrc         self.lcontent=lcont   # \u0434\u0430\u043d\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u043b\u043e\u0433 \u0444\u0430\u0439\u043b\u043e\u043c def main():      i=0     dumpchanstart=False      for line in log_file: #\u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 Dumpchan     \tif 'app_dumpchan.c' in line:             dumpchanstart = 0             for line in log_file:                 if dumpchanstart==2:                     break                  if \"================================================================================\" in line:                     dumpchanstart+=1 #\u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u0438 \u0441\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u043c\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442\u0441\u044f \u0441 [ \tif line[0]==\"[\":           buf=re.split('] ', line) # \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u0438 \u0441 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0439 verbosity           if any(x in buf[1] for x in verbosity):                 buf1spl=buf[1].split('-')                 channum=''                 buf2spl=re.split('c: ',buf[2])                 if len(buf1spl)&gt;1:                     channum=buf1spl[1]                 itemcheck=logitem(buf[0][1:],buf[1].split('[')[0],channum,buf2spl[0],buf2spl[1])                 filtration(itemcheck.lcontent)  #\u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u043e\u0431\u044a\u0435\u043a\u0442 \u0432 \u0441\u043f\u0438\u0441\u043e\u043a matchedregex, \u0435\u0441\u043b\u0438 \u043e\u043d \u043f\u043e\u043f\u0430\u043b \u043f\u043e\u0434 \u0448\u0430\u0431\u043b\u043e\u043d \u0438 \u0432 notmatched, \u0435\u0441\u043b\u0438 \u043d\u0435\u0442.\t def filtration(logitem):      found = False     for regexitem in regexes:          if  re.match(regexitem,logitem):             found= True             matchedregex.append(regexitem)             continue      if found==False:         notmatched.append(logitem)   #\u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0442\u0435\u043b\u0430 \u043f\u0438\u0441\u044c\u043c\u0430. \u0412 \u0431\u043b\u043e\u043a\u0435 if..elif \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u0446\u0432\u0435\u0442\u043e\u0432\u043e\u0435 \u0432\u044b\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0432\u0430\u0436\u043d\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 def genContents():     content=u\"&lt;html&gt;\"     content+=u\"&lt;head&gt;&lt;\/head&gt;\"     content+=u\"&lt;body&gt;\"     content+=u\"&lt;h1&gt;Log digest for \" +servername+ u\"&lt;\/h1&gt;\"     content+=u\"&lt;table  style='width: 60%;' border = \\\"1\\\" cellpadding = \\\"1\\\"'&gt;\"     for key, val in Counter(matchedregex).most_common():         if u\"rejected because extension not found in context\" in key:             key=u\"&lt;b style='background-color:#e8edff'&gt;\"+key+u\"&lt;\/b&gt;\"         elif u\"No matching endpoint found\" in key:             key=u\"&lt;b style='background-color:#fd6161'&gt;\"+key+u\"&lt;\/b&gt;\"         elif u\"No closing parenthesis \" in key:             key = u\"&lt;b style='background-color:#98862a'&gt;\" + key + u\"&lt;\/b&gt;\"         elif u\"Wrong password\" in key:             key=u\"&lt;b style='background-color:#fd6161'&gt;\"+key+u\"&lt;\/b&gt;\"             elif u\"Unterminated comment detected beginning on line\" in key:             key=u\"&lt;b style='background-color:#fd6161'&gt;\"+key+u\"&lt;\/b&gt;\"             content += u\"&lt;tr&gt;\"         content+= u\"&lt;td&gt;\"+key+u\"&lt;\/td&gt;&lt;td&gt;\" +str(val) +u\"&lt;\/td&gt;\"         content += u\"&lt;\/tr&gt;\"     content+=u\"&lt;\/table&gt;&lt;br&gt;\"     content+=u\"&lt;h1&gt;not matched logitems:&lt;\/h1&gt;&lt;br&gt;\"     for item in notmatched:         content+=item     content+=u\"&lt;\/body&gt;\\n\"     content+=u\"&lt;\/html&gt;\\n\"     return  content  main() cont=genContents() yag.send(recipients,subject='Log digest for '+servername,contents=cont) os.remove(workdiir+filename) <\/code><\/pre>\n<p>  \u0437\u0430\u043f\u0443\u0441\u043a \u0441\u043a\u0440\u0438\u043f\u0442\u0430 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439: \/usr\/local\/bin\/ansible-playbook \/etc\/ansible\/playbooks\/parseastlogs.yml<\/p>\n<p>  \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0431\u0443\u0434\u0435\u0442 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0438\u0441\u0435\u043c(\u043f\u043e \u043e\u0434\u043d\u043e\u043c\u0443 \u043f\u0438\u0441\u044c\u043c\u0443 \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438\u0437 \u0433\u0440\u0443\u043f\u043f\u044b production_asterisk) \u0441 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435\u043c:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/dq\/fr\/gd\/dqfrgds3_h245a2_mthf4rc9qhm.png\" alt=\"image\"><\/p>\n<p>  \u0415\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u0438\u043b\u0438 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0433\u043e\u0442\u043e\u0432 \u043d\u0430 \u043d\u0438\u0445 \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c.<\/p><\/div>\n<p>        <script class=\"js-mediator-script\">!function(e){function t(t,n){if(!(n in e)){for(var r,a=e.document,i=a.scripts,o=i.length;o--;)if(-1!==i[o].src.indexOf(t)){r=i[o];break}if(!r){r=a.createElement(\"script\"),r.type=\"text\/javascript\",r.async=!0,r.defer=!0,r.src=t,r.charset=\"UTF-8\";var d=function(){var e=a.getElementsByTagName(\"script\")[0];e.parentNode.insertBefore(r,e)};\"[object Opera]\"==e.opera?a.addEventListener?a.addEventListener(\"DOMContentLoaded\",d,!1):e.attachEvent(\"onload\",d):d()}}}t(\"\/\/mediator.mail.ru\/script\/2820404\/\",\"_mediator\")}(window);<\/script>     <br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/post\/419547\/\"> https:\/\/habr.com\/post\/419547\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div data-io-article-url=\"https:\/\/habr.com\/post\/419547\/\" class=\"post__text post__text-html js-mediator-article\">\u041a\u043e\u0433\u0434\u0430 \u044f \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u043b \u0434\u0430\u043d\u043d\u044b\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442, \u044f \u043d\u0435 \u0431\u044b\u043b \u0437\u043d\u0430\u043a\u043e\u043c \u0441 logwatch. \u041c\u043d\u0435 \u0437\u0430\u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0432\u0438\u0434\u0435\u0442\u044c \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044e \u0441 \u043b\u043e\u0433\u0430\u043c\u0438 \u043d\u0430 \u0441\u0432\u043e\u0438\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u0430\u0445 \u0432 \u0446\u0435\u043b\u043e\u043c, \u0438, \u0442\u0430\u043a \u044f \u0441\u0434\u0435\u043b\u0430\u043b \u044d\u0442\u043e\u0442 \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434. \u0414\u0443\u043c\u0430\u044e, \u0447\u0442\u043e \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u043e\u0447\u044c \u043d\u043e\u0432\u0438\u0447\u043a\u0430\u043c \u0432 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u0438 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439 ansible.<\/p>\n<p>  \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0435 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u044b:<\/p>\n<ul>\n<li>python 2.7.14<\/li>\n<li>ansible 2.3<\/li>\n<li>\u0441\u0435\u0440\u0432\u0435\u0440\u0430 asterisk \u043d\u0430 \u0431\u0430\u0437\u0435 FreePBX 13<\/li>\n<\/ul>\n<p>  \u041c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0434\u0432\u0443\u0445 \u0447\u0430\u0441\u0442\u0435\u0439 \u2013 python \u0441\u043a\u0440\u0438\u043f\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u043b\u043e\u0433 \u0444\u0430\u0439\u043b\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439 \u043e\u0442\u0447\u0435\u0442\u0430 \u043d\u0430 \u043f\u043e\u0447\u0442\u0443, \u0438 \u043f\u043b\u0435\u0439\u0431\u0443\u043a\u0430 \u0434\u043b\u044f \u0441\u0431\u043e\u0440\u0430 \u043b\u043e\u0433\u043e\u0432 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0438\u0445 \u0441\u043a\u0440\u0438\u043f\u0442\u0443 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438.  <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-287552","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/287552","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=287552"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/287552\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=287552"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=287552"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=287552"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}