[{"content":"","date":"11 ноября 2025","externalUrl":null,"permalink":"/","section":"Andrey_Dvur","summary":"","title":"Andrey_Dvur","type":"page"},{"content":"","date":"11 ноября 2025","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"11 ноября 2025","externalUrl":null,"permalink":"/categories/homelab/","section":"Categories","summary":"","title":"Homelab","type":"categories"},{"content":"","date":"11 ноября 2025","externalUrl":null,"permalink":"/tags/homelab/","section":"Tags","summary":"","title":"Homelab","type":"tags"},{"content":"","date":"11 ноября 2025","externalUrl":null,"permalink":"/categories/network/","section":"Categories","summary":"","title":"Network","type":"categories"},{"content":"","date":"11 ноября 2025","externalUrl":null,"permalink":"/tags/network/","section":"Tags","summary":"","title":"Network","type":"tags"},{"content":"","date":"11 ноября 2025","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":" Введение # Proxmox Mail Gateway - шлюз для электронной почты, который стоит до почтового сервера, фильтрует спам, вирусы и пр.\nПро настройку самого Proxmox Mail Gateway документации и видео в интернете достаточно (например чать 1, чать 2).\nCustom check # Почему бы дополнительно к спам-фильтрам не добавить AI, подумал я и решил этим заняться. Напрямую интеграции PMG с какими-либо нейросетями нет, но в документации есть упоминание функции custom check - это скрипт, который выполняется еще до фильтров самого PMG.\nВайб-кодим небольшой скрипт, который будет наши письма анализировать с помощью AI. В качестве провайдера - я использую OpenRouter и небольшую модель mistralai/mistral-small-3.1-24b-instruct.\nИспользуем white-лист чтобы не анализировать доверенные домены и black-лист чтобы повторно не проверять письма с домена, который уже попался на рассылке спама.\n#!/bin/bash # Custom email checker script for Proxmox Mail Gateway # Uses OpenRouter API to analyze emails for spam content set -euo pipefail # Exit on error, undefined variables, and pipe failures # Log file configuration LOG_FILE=\u0026#34;/var/log/pmg-custom-check.log\u0026#34; LOG_MAX_SIZE=10485760 # 10MB # Rotate log file if it exceeds max size rotate_log() { if [ -f \u0026#34;$LOG_FILE\u0026#34; ]; then local size=$(stat -c%s \u0026#34;$LOG_FILE\u0026#34; 2\u0026gt;/dev/null || echo 0) if [ \u0026#34;$size\u0026#34; -gt \u0026#34;$LOG_MAX_SIZE\u0026#34; ]; then mv \u0026#34;$LOG_FILE\u0026#34; \u0026#34;${LOG_FILE}.old\u0026#34; 2\u0026gt;/dev/null || true fi fi } # Log function to write to both STDERR (mail.log) and log file log() { local message message=\u0026#34;$(date \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39;) - $1\u0026#34; # Explicitly write to file descriptor 2 (stderr) and handle any errors { printf \u0026#39;%s\\n\u0026#39; \u0026#34;$message\u0026#34; \u0026gt;\u0026amp;2 } 2\u0026gt;/dev/null || true rotate_log { printf \u0026#39;%s\\n\u0026#39; \u0026#34;$message\u0026#34; \u0026gt;\u0026gt; \u0026#34;$LOG_FILE\u0026#34; } 2\u0026gt;/dev/null || true } # Function to extract email headers extract_email_headers() { local email_file=\u0026#34;$1\u0026#34; # Extract sender (From header) and decode base64 encoding if present local raw_sender=$(grep -m 1 \u0026#39;^From:\u0026#39; \u0026#34;$email_file\u0026#34; 2\u0026gt;/dev/null | sed \u0026#39;s/^From: *//\u0026#39; || echo \u0026#34;unknown\u0026#34;) # Decode MIME encoded words (e.g., =?UTF-8?B?...?= or =?UTF-8?Q?...?=) local sender=\u0026#34;$raw_sender\u0026#34; if [[ \u0026#34;$raw_sender\u0026#34; == *=?* ]]; then # Use Python for more reliable MIME decoding if command -v python3 \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then # Create temporary file approach to avoid complex quoting local temp_py_script=\u0026#34;/tmp/pmg_decode_$$.py\u0026#34; cat \u0026gt; \u0026#34;$temp_py_script\u0026#34; \u0026lt;\u0026lt; \u0026#39;EOF_PY\u0026#39; import sys from email.header import decode_header raw_sender = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;\u0026#34; decoded = decode_header(raw_sender) result = [] for part, encoding in decoded: if isinstance(part, bytes): if encoding: result.append(part.decode(encoding)) else: result.append(part.decode(\u0026#39;utf-8\u0026#39;)) else: result.append(part) print(\u0026#39;\u0026#39;.join(result)) EOF_PY # Ensure the Python script executes safely if [ -f \u0026#34;$temp_py_script\u0026#34; ]; then sender=$(python3 \u0026#34;$temp_py_script\u0026#34; \u0026#34;$raw_sender\u0026#34; 2\u0026gt;/dev/null) # If Python decoding failed, use original value if [ $? -ne 0 ] || [ -z \u0026#34;$sender\u0026#34; ]; then sender=\u0026#34;$raw_sender\u0026#34; fi rm -f \u0026#34;$temp_py_script\u0026#34; else # Fallback if temp script creation failed sender=\u0026#34;$raw_sender\u0026#34; fi fi fi # Extract receiver (To header) local receiver=$(grep -m 1 \u0026#39;^To:\u0026#39; \u0026#34;$email_file\u0026#34; 2\u0026gt;/dev/null | sed \u0026#39;s/^To: *//\u0026#39; || echo \u0026#34;unknown\u0026#34;) # Extract subject (Subject header) local subject=$(grep -m 1 \u0026#39;^Subject:\u0026#39; \u0026#34;$email_file\u0026#34; 2\u0026gt;/dev/null | sed \u0026#39;s/^Subject: *//\u0026#39; || echo \u0026#34;no subject\u0026#34;) # Extract message ID (Message-ID header) - keeping for internal use only local message_id=$(grep -m 1 \u0026#39;^Message-ID:\u0026#39; \u0026#34;$email_file\u0026#34; 2\u0026gt;/dev/null | sed \u0026#39;s/^Message-ID: *//\u0026#39; || echo \u0026#34;no message-id\u0026#34;) # Print the extracted headers for return echo \u0026#34;$sender|$receiver|$subject|$message_id\u0026#34; } # Print usage and exit if arguments are incorrect if [ \u0026#34;$#\u0026#34; -ne 2 ]; then log \u0026#34;usage: $0 APIVERSION QUEUEFILENAME\u0026#34; exit 1 fi apiver=\u0026#34;$1\u0026#34; queue_file=\u0026#34;$2\u0026#34; # Check API version if [ \u0026#34;$apiver\u0026#34; != \u0026#34;v1\u0026#34; ]; then log \u0026#34;wrong APIVERSION: $apiver\u0026#34; exit 2 fi # Extract email headers before processing (needed for logging in error cases) headers_info=$(extract_email_headers \u0026#34;$queue_file\u0026#34;) sender=$(echo \u0026#34;$headers_info\u0026#34; | cut -d\u0026#39;|\u0026#39; -f1) receiver=$(echo \u0026#34;$headers_info\u0026#34; | cut -d\u0026#39;|\u0026#39; -f2) subject=$(echo \u0026#34;$headers_info\u0026#34; | cut -d\u0026#39;|\u0026#39; -f3) message_id=$(echo \u0026#34;$headers_info\u0026#34; | cut -d\u0026#39;|\u0026#39; -f4) # Whitelist/Blacklist functionality check_whitelist_blacklist() { local sender=\u0026#34;$1\u0026#34; local whitelist_file=\u0026#34;$(dirname \u0026#34;$0\u0026#34;)/whitelist.txt\u0026#34; local blacklist_file=\u0026#34;$(dirname \u0026#34;$0\u0026#34;)/blacklist.txt\u0026#34; # Check if sender matches whitelist if [ -f \u0026#34;$whitelist_file\u0026#34; ]; then while IFS= read -r pattern; do # Skip empty lines [ -z \u0026#34;$pattern\u0026#34; ] \u0026amp;\u0026amp; continue if echo \u0026#34;$sender\u0026#34; | grep -qi \u0026#34;$pattern\u0026#34;; then log \u0026#34;Sender in whitelist: $sender - returning SCORE: 0\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;SCORE: 0\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi done \u0026lt; \u0026#34;$whitelist_file\u0026#34; fi # Check if sender matches blacklist if [ -f \u0026#34;$blacklist_file\u0026#34; ]; then while IFS= read -r pattern; do # Skip empty lines [ -z \u0026#34;$pattern\u0026#34; ] \u0026amp;\u0026amp; continue if echo \u0026#34;$sender\u0026#34; | grep -qi \u0026#34;$pattern\u0026#34;; then log \u0026#34;Sender in blacklist: $sender - returning SCORE: 10\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;SCORE: 10\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi done \u0026lt; \u0026#34;$blacklist_file\u0026#34; fi } # Check whitelist/blacklist before proceeding with API check check_whitelist_blacklist \u0026#34;$sender\u0026#34; # Check if input file exists if [ ! -f \u0026#34;$queue_file\u0026#34; ]; then log \u0026#34;queue file does not exist: $queue_file\u0026#34; # Output API version and result for missing file to STDOUT explicitly { printf \u0026#39;%s\\n\u0026#39; \u0026#34;v1\u0026#34; \u0026gt;\u0026amp;1 printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi # Configuration for OpenRouter API OPENROUTER_API_KEY=\u0026#34;sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u0026#34; OPENROUTER_MODEL=\u0026#34;mistralai/mistral-small-3.1-24b-instruct\u0026#34; if [ -z \u0026#34;$OPENROUTER_API_KEY\u0026#34; ]; then log \u0026#34;ERROR: OPENROUTER_API_KEY environment variable not set, cannot proceed with API check - Sender: $sender, Receiver: $receiver\u0026#34; # Output API version and result if no API key to STDOUT explicitly { printf \u0026#39;%s\\n\u0026#39; \u0026#34;v1\u0026#34; \u0026gt;\u0026amp;1 printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi # Output API version once all validation is done to STDOUT explicitly { printf \u0026#39;%s\\n\u0026#39; \u0026#34;v1\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true # Read the email content email_content=\u0026#34;\u0026#34; if [ -r \u0026#34;$queue_file\u0026#34; ]; then email_content=$(cat \u0026#34;$queue_file\u0026#34;) log \u0026#34;Successfully read email content - Sender: $sender, Receiver: $receiver, Size: $(echo -n \u0026#34;$email_content\u0026#34; | wc -c) bytes\u0026#34; else log \u0026#34;ERROR: cannot read queue file: $queue_file - Sender: $sender, Receiver: $receiver\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi # Function to decode MIME encoded subject (similar to the one used in extract_email_headers) decode_mime_subject() { local raw_subject=\u0026#34;$1\u0026#34; local decoded_subject=\u0026#34;$raw_subject\u0026#34; # Check if subject is MIME encoded (contains =?...?= pattern) if [[ \u0026#34;$raw_subject\u0026#34; == *=?* ]]; then # Use Python for reliable MIME decoding if command -v python3 \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then local temp_py_script=\u0026#34;/tmp/pmg_decode_subject_$$.py\u0026#34; cat \u0026gt; \u0026#34;$temp_py_script\u0026#34; \u0026lt;\u0026lt; \u0026#39;EOF_PY\u0026#39; import sys from email.header import decode_header raw_subject = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;\u0026#34; decoded = decode_header(raw_subject) result = [] for part, encoding in decoded: if isinstance(part, bytes): if encoding: result.append(part.decode(encoding)) else: result.append(part.decode(\u0026#39;utf-8\u0026#39;)) else: result.append(part) print(\u0026#39;\u0026#39;.join(result)) EOF_PY if [ -f \u0026#34;$temp_py_script\u0026#34; ]; then decoded_subject=$(python3 \u0026#34;$temp_py_script\u0026#34; \u0026#34;$raw_subject\u0026#34; 2\u0026gt;/dev/null) if [ $? -ne 0 ] || [ -z \u0026#34;$decoded_subject\u0026#34; ]; then decoded_subject=\u0026#34;$raw_subject\u0026#34; fi rm -f \u0026#34;$temp_py_script\u0026#34; fi fi fi echo \u0026#34;$decoded_subject\u0026#34; } # Decode the subject for logging decoded_subject=$(decode_mime_subject \u0026#34;$subject\u0026#34;) # Log email details before processing with decoded subject log \u0026#34;Processing email - Sender: $sender, Receiver: $receiver, Subject: $decoded_subject\u0026#34; # Limit email size to prevent API errors (e.g., 100KB limit) EMAIL_SIZE_LIMIT=52400 # 100KB in bytes (100 * 1024) email_size=$(echo -n \u0026#34;$email_content\u0026#34; | wc -c) if [ \u0026#34;$email_size\u0026#34; -gt \u0026#34;$EMAIL_SIZE_LIMIT\u0026#34; ]; then log \u0026#34;Email too large ($email_size bytes), truncating to $EMAIL_SIZE_LIMIT bytes - Sender: $sender, Receiver: $receiver\u0026#34; # Truncate email content, handling potential pipe issues email_content=$(echo \u0026#34;$email_content\u0026#34; | head -c \u0026#34;$EMAIL_SIZE_LIMIT\u0026#34; 2\u0026gt;/dev/null || echo \u0026#34;${email_content:0:$EMAIL_SIZE_LIMIT}\u0026#34;) fi # Prepare the request to OpenRouter API request_body=$(cat \u0026lt;\u0026lt;EOF { \u0026#34;model\u0026#34;: \u0026#34;$OPENROUTER_MODEL\u0026#34;, \u0026#34;messages\u0026#34;: [ { \u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;You are an email spam classifier. Analyze the following email and determine if it is spam. Respond with a JSON object containing \u0026#39;is_spam\u0026#39; (boolean) and \u0026#39;confidence\u0026#39; (number between 0 and 1) and \u0026#39;reason\u0026#39; (string explaining why). Be strict in your assessment - only mark as spam if there are clear signs of unwanted or malicious content.\u0026#34; }, { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Please analyze this email for spam content:\\n\\n$email_content\u0026#34; } ], \u0026#34;provider\u0026#34;: { \u0026#34;sort\u0026#34;: \u0026#34;price\u0026#34; } } EOF ) # Log that we\u0026#39;re about to make the API call log \u0026#34;About to call OpenRouter API - Sender: $sender, Receiver: $receiver\u0026#34; # Use cURL to call the OpenRouter API response=$(curl -s -X POST \\ -H \u0026#34;Authorization: Bearer $OPENROUTER_API_KEY\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#34;$request_body\u0026#34; \\ \u0026#34;https://openrouter.ai/api/v1/chat/completions\u0026#34; \\ --max-time 240) # 4 minute timeout to stay under the 5-minute limit if [ $? -ne 0 ]; then log \u0026#34;Failed to get response from OpenRouter API - Sender: $sender, Receiver: $receiver\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi if [ -z \u0026#34;$response\u0026#34; ]; then log \u0026#34;Empty response from OpenRouter API - Sender: $sender, Receiver: $receiver\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi # Check if jq is available before using it if ! command -v jq \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log \u0026#34;ERROR: jq command not found - Sender: $sender, Receiver: $receiver\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 exit 0 fi response_size=$(echo -n \u0026#34;$response\u0026#34; | wc -c) log \u0026#34;Successfully received response from OpenRouter API - Sender: $sender, Receiver: $receiver, Response size: $response_size bytes\u0026#34; # Check if the response contains an error, but first check if jq is available if command -v jq \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then if echo \u0026#34;$response\u0026#34; | jq -e \u0026#39;.error\u0026#39; \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then error_message=$(echo \u0026#34;$response\u0026#34; | jq -r \u0026#39;.error.message // \u0026#34;Unknown API error\u0026#34;\u0026#39;) error_code=$(echo \u0026#34;$response\u0026#34; | jq -r \u0026#39;.error.code // \u0026#34;Unknown code\u0026#34;\u0026#39;) log \u0026#34;OpenRouter API error - Sender: $sender, Receiver: $receiver, Error: $error_message, Code: $error_code\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 exit 0 fi else log \u0026#34;ERROR: jq command not found during error checking - Sender: $sender, Receiver: $receiver\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi # Extract the AI\u0026#39;s response (need to extract the text content from the completion) ai_response=$(echo \u0026#34;$response\u0026#34; | jq -r \u0026#39;.choices[0].message.content\u0026#39; 2\u0026gt;/dev/null) if [ -z \u0026#34;$ai_response\u0026#34; ] || [ \u0026#34;$ai_response\u0026#34; = \u0026#34;null\u0026#34; ]; then log \u0026#34;Could not extract response from OpenRouter API - Sender: $sender, Receiver: $receiver, Response: $response\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi # Check if sed is available before extracting JSON if ! command -v sed \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log \u0026#34;ERROR: sed command not found - Sender: $sender, Receiver: $receiver\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi # Check if sed is available before extracting JSON if ! command -v sed \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log \u0026#34;ERROR: sed command not found - Sender: $sender, Receiver: $receiver\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi # Try to extract JSON from the AI response (sometimes it\u0026#39;s wrapped in markdown or additional text) # Handle both markdown-wrapped JSON (```json ... ```) and plain JSON json_response=\u0026#34;\u0026#34; if echo \u0026#34;$ai_response\u0026#34; | grep -q \u0026#39;```json\u0026#39;; then # Extract JSON from markdown code block json_response=$(echo \u0026#34;$ai_response\u0026#34; | sed -n \u0026#39;/```json/,/```/p\u0026#39; | head -n -1 | tail -n +2) elif echo \u0026#34;$ai_response\u0026#34; | grep -E -q \u0026#39;\\{.*\\}\u0026#39;; then # Extract first JSON object found json_response=$(echo \u0026#34;$ai_response\u0026#34; | sed -n \u0026#39;/{/,/}/p\u0026#39; | sed -n 1p) fi # If extraction failed using above methods, try more flexible approach if [ -z \u0026#34;$json_response\u0026#34; ] || [ \u0026#34;$json_response\u0026#34; = \u0026#34;null\u0026#34; ]; then # Find content between first { and last } if command -v grep \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then if echo \u0026#34;$ai_response\u0026#34; | grep -o \u0026#39;{.*}\u0026#39; \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then json_response=$(echo \u0026#34;$ai_response\u0026#34; | grep -o \u0026#39;{[^}]*}\u0026#39; | head -n 1) fi else log \u0026#34;ERROR: grep command not found during JSON extraction - Sender: $sender, Receiver: $receiver\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi fi # Store the OpenRouter response for later use openrouter_analysis=\u0026#34;$ai_response\u0026#34; # Log the complete OpenRouter analysis result log \u0026#34;OpenRouter Analysis Result - Sender: $sender, Receiver: $receiver, Result: $ai_response\u0026#34; # Detailed logging of the analysis results if [ -n \u0026#34;$json_response\u0026#34; ] \u0026amp;\u0026amp; [ \u0026#34;$json_response\u0026#34; != \u0026#34;null\u0026#34; ]; then # Check if jq is still available before parsing JSON response if command -v jq \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then is_spam=$(echo \u0026#34;$json_response\u0026#34; | jq -r \u0026#39;.is_spam\u0026#39; 2\u0026gt;/dev/null) confidence=$(echo \u0026#34;$json_response\u0026#34; | jq -r \u0026#39;.confidence\u0026#39; 2\u0026gt;/dev/null) reason=$(echo \u0026#34;$json_response\u0026#34; | jq -r \u0026#39;.reason\u0026#39; 2\u0026gt;/dev/null) log \u0026#34;Spam Analysis Result - Sender: $sender, Receiver: $receiver, Is Spam: $is_spam, Confidence: $confidence, Reason: $reason\u0026#34; else log \u0026#34;ERROR: jq command not found during JSON parsing - Sender: $sender, Receiver: $receiver\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi else log \u0026#34;Spam Analysis Result - Sender: $sender, Receiver: $receiver, Plain Text Analysis: $ai_response\u0026#34; fi if [ -n \u0026#34;$json_response\u0026#34; ] \u0026amp;\u0026amp; [ \u0026#34;$json_response\u0026#34; != \u0026#34;null\u0026#34; ]; then # Parse the JSON response - check if jq is still available if command -v jq \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then is_spam=$(echo \u0026#34;$json_response\u0026#34; | jq -r \u0026#39;.is_spam\u0026#39; 2\u0026gt;/dev/null) confidence=$(echo \u0026#34;$json_response\u0026#34; | jq -r \u0026#39;.confidence\u0026#39; 2\u0026gt;/dev/null) reason=$(echo \u0026#34;$json_response\u0026#34; | jq -r \u0026#39;.reason\u0026#39; 2\u0026gt;/dev/null) else log \u0026#34;ERROR: jq command not found during JSON response parsing - Sender: $sender, Receiver: $receiver\u0026#34; printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 exit 0 fi if [ \u0026#34;$is_spam\u0026#34; = \u0026#34;true\u0026#34; ]; then log \u0026#34;SPAM DETECTED - Sender: $sender, Receiver: $receiver, Confidence: $confidence, Reason: $reason, OpenRouter Analysis: $openrouter_analysis\u0026#34; # Calculate score from confidence score=$(awk \u0026#34;BEGIN {printf \\\u0026#34;%.0f\\\u0026#34;, $confidence * 10}\u0026#34;) # Extract domain from sender email address domain=$(echo \u0026#34;$sender\u0026#34; | grep -oE \u0026#39;@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}\u0026#39; | sed \u0026#39;s/^@//\u0026#39; | head -n 1) if [ -n \u0026#34;$domain\u0026#34; ]; then blacklist_file=\u0026#34;$(dirname \u0026#34;$0\u0026#34;)/blacklist.txt\u0026#34; # Check if domain is already in blacklist before adding if [ ! -f \u0026#34;$blacklist_file\u0026#34; ] || ! grep -Fxq \u0026#34;$domain\u0026#34; \u0026#34;$blacklist_file\u0026#34;; then log \u0026#34;Confirmed spam detected, adding domain $domain to blacklist\u0026#34; # Try to add to blacklist, but ignore errors { echo \u0026#34;$domain\u0026#34; \u0026gt;\u0026gt; \u0026#34;$blacklist_file\u0026#34; } 2\u0026gt;/dev/null || true fi fi # Output the score result and exit immediately { printf \u0026#39;%s\\n\u0026#39; \u0026#34;SCORE: $score\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 elif [ -n \u0026#34;$confidence\u0026#34; ] \u0026amp;\u0026amp; [ \u0026#34;$confidence\u0026#34; != \u0026#34;null\u0026#34; ]; then # Check if awk is available for score calculation if ! command -v awk \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log \u0026#34;ERROR: awk command not found - Sender: $sender, Receiver: $receiver\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi # Convert confidence to a spam score between 0 and 10 using awk instead of bc score=$(awk \u0026#34;BEGIN {printf \\\u0026#34;%.0f\\\u0026#34;, $confidence * 10}\u0026#34;) log \u0026#34;Spam analysis completed - Sender: $sender, Receiver: $receiver, Confidence: $confidence, Score: $score, OpenRouter Analysis: $openrouter_analysis\u0026#34; # Only add score if it\u0026#39;s meaningful (at least 0.3 confidence) if (( $(awk \u0026#34;BEGIN {print ($confidence \u0026gt; 0.3)}\u0026#34;) )); then # If score is 7 or higher, add domain to blacklist if [ \u0026#34;$score\u0026#34; -ge 7 ]; then # Extract domain from sender email address domain=$(echo \u0026#34;$sender\u0026#34; | grep -oE \u0026#39;@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}\u0026#39; | sed \u0026#39;s/^@//\u0026#39; | head -n 1) if [ -n \u0026#34;$domain\u0026#34; ]; then blacklist_file=\u0026#34;$(dirname \u0026#34;$0\u0026#34;)/blacklist.txt\u0026#34; # Check if domain is already in blacklist before adding if [ ! -f \u0026#34;$blacklist_file\u0026#34; ] || ! grep -Fxq \u0026#34;$domain\u0026#34; \u0026#34;$blacklist_file\u0026#34;; then log \u0026#34;Score $score \u0026gt;= 7, adding domain $domain to blacklist\u0026#34; # Try to add to blacklist, but ignore errors { echo \u0026#34;$domain\u0026#34; \u0026gt;\u0026gt; \u0026#34;$blacklist_file\u0026#34; } 2\u0026gt;/dev/null || true fi fi fi # Output the score result and exit immediately { printf \u0026#39;%s\\n\u0026#39; \u0026#34;SCORE: $score\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 else # Output OK result and exit immediately { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi fi else # If JSON parsing fails, try to infer from plain text response # Check if grep is available before using it if ! command -v grep \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then log \u0026#34;ERROR: grep command not found - Sender: $sender, Receiver: $receiver\u0026#34; { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi if echo \u0026#34;$ai_response\u0026#34; | grep -qi \u0026#34;spam\\|phishing\\|malware\\|virus\\|suspicious\u0026#34;; then log \u0026#34;SPAM DETECTED in plain text analysis - Sender: $sender, Receiver: $receiver, Analysis: $ai_response, OpenRouter Analysis: $openrouter_analysis\u0026#34; # Extract domain from sender email address domain=$(echo \u0026#34;$sender\u0026#34; | grep -oE \u0026#39;@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}\u0026#39; | sed \u0026#39;s/^@//\u0026#39; | head -n 1) if [ -n \u0026#34;$domain\u0026#34; ]; then blacklist_file=\u0026#34;$(dirname \u0026#34;$0\u0026#34;)/blacklist.txt\u0026#34; # Check if domain is already in blacklist before adding if [ ! -f \u0026#34;$blacklist_file\u0026#34; ] || ! grep -Fxq \u0026#34;$domain\u0026#34; \u0026#34;$blacklist_file\u0026#34;; then log \u0026#34;High confidence spam detected, adding domain $domain to blacklist\u0026#34; # Try to add to blacklist, but ignore errors { echo \u0026#34;$domain\u0026#34; \u0026gt;\u0026gt; \u0026#34;$blacklist_file\u0026#34; } 2\u0026gt;/dev/null || true fi fi # Output the score result and exit immediately { printf \u0026#39;%s\\n\u0026#39; \u0026#34;SCORE: 10\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 fi fi # Final log and result output - make sure nothing in this section fails log \u0026#34;Email passed spam check - Sender: $sender, Receiver: $receiver, OpenRouter Analysis: $openrouter_analysis\u0026#34; 2\u0026gt;/dev/null || true { printf \u0026#39;%s\\n\u0026#39; \u0026#34;OK\u0026#34; \u0026gt;\u0026amp;1 } 2\u0026gt;/dev/null || true exit 0 Дополнения # Скрипт должен находится по адресу /usr/local/bin/pmg-custom-check, в этой же папке whitelist.txt и blacklist.txt\nА в /etc/pmg/pmg.conf должен быть включен функционал cutsom check\nsection: admin custom_check 1 И чтобы два раза не вставать, в PMG у меня уже есть какие-то домены в whitelist|blacklist, я решил их сразу добавить сюда. Сначала воспользуемся командой\nsudo pmgsh get /config/ruledb/who чтобы определить ID, созданных в PMG whitelist|blacklist, затем экспортируем домены во временные файлы\nsudo pmgsh get /config/ruledb/who/2/objects | jq \u0026#39;.[] | select(.domain != null) | .domain\u0026#39; \u0026gt; /tmp/blacklist.text sudo pmgsh get /config/ruledb/who/3/objects | jq \u0026#39;.[] | select(.domain != null) | .domain\u0026#39; \u0026gt; /tmp/whitelist.txt После чего можем добавить их в /usr/local/bin/\nИтог # Прикрутил AI к проверке почты. Я использовал OpenRouter и модель mistralai/mistral-small-3.1-24b-instruct (достаточно бюджетная). Сейчас я использую этот скрипт на не очень критичной почте, не уверен, что готов отправлять нейронке все свои сообщения. Можно использовать и какие-нибудь локальные llama-модели, но подходящего железа под рукой нет, а протестировать концепцию использования AI для анализа спама хотелось.\n","date":"11 ноября 2025","externalUrl":null,"permalink":"/posts/2025-11-11-pmg-custom-check/","section":"Статьи","summary":"","title":"ИИ Антиспам на Proxmox Mail Gateway","type":"posts"},{"content":"","date":"11 ноября 2025","externalUrl":null,"permalink":"/posts/","section":"Статьи","summary":"","title":"Статьи","type":"posts"},{"content":"","date":"5 марта 2025","externalUrl":null,"permalink":"/categories/ansible/","section":"Categories","summary":"","title":"Ansible","type":"categories"},{"content":"","date":"5 марта 2025","externalUrl":null,"permalink":"/tags/ansible/","section":"Tags","summary":"","title":"Ansible","type":"tags"},{"content":"","date":"5 марта 2025","externalUrl":null,"permalink":"/manuals/","section":"Manuals","summary":"","title":"Manuals","type":"manuals"},{"content":"","date":"5 марта 2025","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"5 марта 2025","externalUrl":null,"permalink":"/series/%D0%B2%D0%B2%D0%BE%D0%B4%D0%BD%D1%8B%D0%B9-%D0%BA%D1%83%D1%80%D1%81-ansible/","section":"Series","summary":"","title":"Вводный Курс Ansible","type":"series"},{"content":" Курс Ansible - урок 16 - Шаблоны # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач.\nРасположение файла конфигурации демона OpenSSH (используется в качестве примера для шаблона) # /etc/ssh/sshd_config В каталоге ролей # Создайте каталог для шаблонов:\nmkdir templates Скопируйте файл конфигурации sshd с сервера в этот каталог с расширением *.j2:\nsshd_config_ubuntu.j2 Примечание: «ubuntu» включено в название, потому что, если вы используете несколько дистрибутивов, лучше начать создание шаблона на основе существующего файла. У каждого дистрибутива свои пути, поэтому НЕ используйте один и тот же шаблон для всех дистрибутивов!\nДобавление переменной внутри шаблона конфигурационного файла OpenSSH # Внутри шаблона (шаблонов) добавьте:\nAllowUsers Template:Ssh users Примечание: убедитесь, что параметр «AllowUsers» уже не указан в файле. Если он указан, замените его на приведенный выше)\nДобавление переменной к хосту # В каталоге host_vars у нас уже должен быть файл с переменными хостов для каждого сервера. Добавьте новую переменную в файл (файлы):\nssh_users: \u0026#34;jay simone\u0026#34; ssh_template_file: sshd_config_ubuntu.j2 Примечание: убедитесь, что вы изменили имена пользователей в переменной ssh_users на фактические имена пользователей, которые вы используете для себя, а также для пользователя Ansible.\nПерепишем роль с учтом шаблона # Внутри базовой роли, в файле main.yml, добавьте эту строку:\n- name: openssh | generate sshd_config file from template tags: ssh template: src: \u0026#34;Template:Ssh template file\u0026#34; dest: /etc/ssh/sshd_config owner: root group: root mode: 0644 notify: restart_sshd Создайте обработчик для перезапуска демона OpenSSH # Внутри базовой роли создайте каталог для обработчиков:\nmkdir handlers Зайдите в этот каталог и создайте файл ‘main.yml’:\ncd handlers vim main.yml - name: restart_sshd service: name: sshd state: restarted Запустите плейбук # ansible-playbook site.yml Добавление значения по умолчанию к переменной # В шаблоне измените строку “AllowUsers” на:\nAllowUsers Template:Ssh users Если пойти дальше, можно добавить в шаблон ещё одну переменную, чтобы определить, разрешена ли аутентификация по паролю для хоста:\nPasswordAuthentication Template:Passwd auth Примечание: не забудьте добавить новую переменную PasswordAuthentication в файлы host_vars.\nОригинал\n","date":"5 марта 2025","externalUrl":null,"permalink":"/manuals/ansible-part16-templates/","section":"Manuals","summary":"","title":"Курс Ansible - урок 16 - Шаблоны","type":"manuals"},{"content":"","date":"5 марта 2025","externalUrl":null,"permalink":"/categories/%D1%83%D1%87%D0%B5%D0%B1%D0%BD%D0%B8%D0%BA/","section":"Categories","summary":"","title":"Учебник","type":"categories"},{"content":"","date":"5 марта 2025","externalUrl":null,"permalink":"/tags/%D1%83%D1%87%D0%B5%D0%B1%D0%BD%D0%B8%D0%BA/","section":"Tags","summary":"","title":"Учебник","type":"tags"},{"content":" Курс Ansible - урок 15 - Переменные хостов # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач.\nhost_vars (ubuntu web server) # apache_package_name: apache2 apache_service: apache2 php_package_name: libapache2-mod-php host_vars (ubuntu web server) # apache_package_name: httpd apache_service: httpd php_package_name: php main.yml (роль web_servers) # - name: install web server packages tags: apache,apache2,centos,httpd,ubuntu package: name: - \u0026#34;Template:Apache package name\u0026#34; - \u0026#34;Template:Php package name\u0026#34; state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: start and enable apache tags: apache,centos,httpd service: name: \u0026#34;Template:Apache service\u0026#34; state: started enabled: yes - name: change e-mail address for admin tags: apache,centos,httpd lineinfile: path: /etc/httpd/conf/httpd.conf regexp: \u0026#39;^ServerAdmin\u0026#39; line: ServerAdmin somebody@somewhere.net when: ansible_distribution == \u0026#34;CentOS\u0026#34; notify: restart_apache - name: copy html file for site tags: apache,apache,apache2,httpd copy: src: default_site.html dest: /var/www/html/index.html owner: root group: root mode: 0644 Обработчики для роли web_servers # Создайте каталог обработчиков (в каталоге ролей):\nmkdir handlers - name: restart_apache tags: apache,centos,httpd service: name: \u0026#34;Template:Apache service\u0026#34; state: restarted Оригинал\n","date":"3 марта 2025","externalUrl":null,"permalink":"/manuals/ansible-part15-host-variables/","section":"Manuals","summary":"","title":"Курс Ansible - урок 15 - Переменные хостов","type":"manuals"},{"content":" Курс Ansible - урок 14 - Роли # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач.\nsite.yml (новая версия) # --- - hosts: all become: true pre_tasks: - name: update repository index (CentOS) tags: always dnf: update_cache: yes changed_when: false when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: update repository index (Ubuntu) tags: always apt: update_cache: yes changed_when: false when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: all become: true roles: - base - hosts: workstations become: true roles: - workstations - hosts: web_servers become: true roles: - web_servers - hosts: db_servers become: true roles: - db_servers - hosts: file_servers become: true roles: - file_servers Создаем каталог ролей # mkdir roles Создаем каталог для каждой роли, которую хотим добавить: # cd roles mkdir base mkdir db_servers mkdir file_servers mkdir web_servers mkdir workstations Внутри каталога каждой роли создаем каталог задач # cd \u0026lt;role_name\u0026gt; mkdir tasks main.yml (основная роль) # Используйте свой ключ в последней строчке, взамен указанного здесь {: .prompt-tip }\n- name: add ssh key for simone authorized_key: user: simone key: \u0026#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAe7/ofWLNBq3+fRn3UmgAizdicLs9vcS4Oj8VSOD1S/ ansible\u0026#34; Создадим необходимые файлы/каталоги для роли db_servers # cd .. cd .. mkdir db_servers cd db_servers mkdir tasks cd tasks vim main.yml main.yml (роль db_servers) # - name: install mariadb server package (CentOS) tags: centos,db,mariadb dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server tags: db,mariadb,ubuntu apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; main.yml (роль file_servers) # - name: install samba package tags: samba package: name: samba state: latest main.yml (роль workstations) # - name: install unzip package: name: unzip - name: install terraform unarchive: src: https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip dest: /usr/local/bin remote_src: yes mode: 0755 owner: root group: root main.yml (роль web_servers) # - name: install httpd package (CentOS) tags: apache,centos,httpd dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: start and enable httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: started enabled: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) tags: apache,apache2,ubuntu apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: change e-mail address for admin tags: apache,centos,httpd lineinfile: path: /etc/httpd/conf/httpd.conf regexp: \u0026#39;^ServerAdmin\u0026#39; line: ServerAdmin somebody@somewhere.net when: ansible_distribution == \u0026#34;CentOS\u0026#34; register: httpd - name: restart httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: restarted when: httpd.changed - name: copy html file for site tags: apache,apache,apache2,httpd copy: src: default_site.html dest: /var/www/html/index.html owner: root group: root mode: 0644 Запускаем новый плейбук # ansible-playbook site.yml Оригинал\n","date":"14 февраля 2025","externalUrl":null,"permalink":"/manuals/ansible-part14-roles/","section":"Manuals","summary":"","title":"Курс Ansible - урок 14 - Роли","type":"manuals"},{"content":" Курс Ansible - урок 13 - Управление пользователями # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач.\nsite.yml (добавлена секция создания пользователя) # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) tags: always dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) tags: always apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: all become: true tasks: - name: create simone user tags: always user: name: simone groups: root - hosts: workstations become: true tasks: - name: install unzip package: name: unzip - name: install terraform unarchive: src: https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip dest: /usr/local/bin remote_src: yes mode: 0755 owner: root group: root - hosts: web_servers become: true tasks: - name: install httpd package (CentOS) tags: apache,centos,httpd dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: start and enable httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: started enabled: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) tags: apache,apache2,ubuntu apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: change e-mail address for admin tags: apache,centos,httpd lineinfile: path: /etc/httpd/conf/httpd.conf regexp: \u0026#39;^ServerAdmin\u0026#39; line: ServerAdmin somebody@somewhere.net when: ansible_distribution == \u0026#34;CentOS\u0026#34; register: httpd - name: restart httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: restarted when: httpd.changed - name: copy html file for site tags: apache,apache,apache2,httpd copy: src: default_site.html dest: /var/www/html/index.html owner: root group: root mode: 0644 - hosts: db_servers become: true tasks: - name: install mariadb server package (CentOS) tags: centos,db,mariadb dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server tags: db,mariadb,ubuntu apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: file_servers tags: samba become: true tasks: - name: install samba package tags: samba package: name: samba state: latest sudoer_simone # simone ALL=(ALL) NOPASSWD: ALL site.yml (копируем файл sudoer) # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) tags: always dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) tags: always apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: all become: true tasks: - name: create simone user user: name: simone groups: root - name: add ssh key for simone tags: always authorized_key: user: simone key: \u0026#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAe7/ofWLNBq3+fRn3UmgAizdicLs9vcS4Oj8VSOD1S/ ansible\u0026#34; - name: add sudoers file for simone tags: always copy: src: sudoer_simone dest: /etc/sudoers.d/simone owner: root group: root mode: 0440 - hosts: workstations become: true tasks: - name: install unzip package: name: unzip - name: install terraform unarchive: src: https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip dest: /usr/local/bin remote_src: yes mode: 0755 owner: root group: root - hosts: web_servers become: true tasks: - name: install httpd package (CentOS) tags: apache,centos,httpd dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: start and enable httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: started enabled: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) tags: apache,apache2,ubuntu apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: change e-mail address for admin tags: apache,centos,httpd lineinfile: path: /etc/httpd/conf/httpd.conf regexp: \u0026#39;^ServerAdmin\u0026#39; line: ServerAdmin somebody@somewhere.net when: ansible_distribution == \u0026#34;CentOS\u0026#34; register: httpd - name: restart httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: restarted when: httpd.changed - name: copy html file for site tags: apache,apache,apache2,httpd copy: src: default_site.html dest: /var/www/html/index.html owner: root group: root mode: 0644 - hosts: db_servers become: true tasks: - name: install mariadb server package (CentOS) tags: centos,db,mariadb dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server tags: db,mariadb,ubuntu apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: file_servers tags: samba become: true tasks: - name: install samba package tags: samba package: name: samba state: latest bootstrap.yml # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) tags: always dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) tags: always apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: all become: true tasks: - name: create simone user user: name: simone groups: root - name: add ssh key for simone authorized_key: user: simone key: \u0026#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAe7/ofWLNBq3+fRn3UmgAizdicLs9vcS4Oj8VSOD1S/ ansible\u0026#34; - name: add sudoers file for simone copy: src: sudoer_simone dest: /etc/sudoers.d/simone owner: root group: root mode: 0440 site.yml (финальная версия) # --- - hosts: all become: true pre_tasks: - name: update repository index (CentOS) tags: always dnf: update_cache: yes changed_when: false when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: update repository index (Ubuntu) tags: always apt: update_cache: yes changed_when: false when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: all become: true tasks: - name: add ssh key for simone authorized_key: user: simone key: \u0026#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAe7/ofWLNBq3+fRn3UmgAizdicLs9vcS4Oj8VSOD1S/ ansible\u0026#34; - hosts: workstations become: true tasks: - name: install unzip package: name: unzip - name: install terraform unarchive: src: https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip dest: /usr/local/bin remote_src: yes mode: 0755 owner: root group: root - hosts: web_servers become: true tasks: - name: install httpd package (CentOS) tags: apache,centos,httpd dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: start and enable httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: started enabled: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) tags: apache,apache2,ubuntu apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: change e-mail address for admin tags: apache,centos,httpd lineinfile: path: /etc/httpd/conf/httpd.conf regexp: \u0026#39;^ServerAdmin\u0026#39; line: ServerAdmin somebody@somewhere.net when: ansible_distribution == \u0026#34;CentOS\u0026#34; register: httpd - name: restart httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: restarted when: httpd.changed - name: copy html file for site tags: apache,apache,apache2,httpd copy: src: default_site.html dest: /var/www/html/index.html owner: root group: root mode: 0644 - hosts: db_servers become: true tasks: - name: install mariadb server package (CentOS) tags: centos,db,mariadb dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server tags: db,mariadb,ubuntu apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: file_servers tags: samba become: true tasks: - name: install samba package tags: samba package: name: samba state: latest Оригинал\n","date":"13 февраля 2025","externalUrl":null,"permalink":"/manuals/ansible-part13-managing-users/","section":"Manuals","summary":"","title":"Курс Ansible - урок 13 - Управление пользователями","type":"manuals"},{"content":" Курс Ansible - урок 11 - Управление файлами # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач.\ndefault_site.html # Сохраните этот файл в каталоге с именем «files» в корневом каталоге репозитория. {: .prompt-tip }\n\u0026lt;html\u0026gt; \u0026lt;title\u0026gt;Web-site test\u0026lt;/title\u0026gt; \u0026lt;body\u0026gt; Ansible is awesome! \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; site.yml (обновлен для копирования default_site.html) # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) tags: always dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) tags: always apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: web_servers become: true tasks: - name: install httpd package (CentOS) tags: apache,centos,httpd dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) tags: apache,apache2,ubuntu apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: copy html file for site tags: apache,apache,apache2,httpd copy: src: default_site.html dest: /var/www/html/index.html owner: root group: root mode: 0644 - hosts: db_servers become: true tasks: - name: install mariadb server package (CentOS) tags: centos,db,mariadb dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server tags: db,mariadb,ubuntu apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: file_servers tags: samba become: true tasks: - name: install samba package tags: samba package: name: samba state: latest Запустите плейбук # ansible-playbook --ask-become-pass file_management.yml file_management.yml (обновлено) # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) tags: always dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) tags: always apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: workstations become: true tasks: - name: install unzip package: name: unzip - name: install terraform unarchive: src: https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip dest: /usr/local/bin remote_src: yes mode: 0755 owner: root group: root - hosts: web_servers become: true tasks: - name: install httpd package (CentOS) tags: apache,centos,httpd dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) tags: apache,apache2,ubuntu apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: copy html file for site tags: apache,apache,apache2,httpd copy: src: default_site.html dest: /var/www/html/index.html owner: root group: root mode: 0644 - hosts: db_servers become: true tasks: - name: install mariadb server package (CentOS) tags: centos,db,mariadb dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server tags: db,mariadb,ubuntu apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: file_servers tags: samba become: true tasks: - name: install samba package tags: samba package: name: samba state: latest Запустите обновленный плейбук # ansible-playbook --ask-become-pass file_management.yml Оригинал\n","date":"4 февраля 2025","externalUrl":null,"permalink":"/manuals/ansible-part11-managing-files/","section":"Manuals","summary":"","title":"Курс Ansible - урок 11 - Управление файлами","type":"manuals"},{"content":" Курс Ansible - урок 12 - Управление службами # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач.\nsite.yml # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) tags: always dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) tags: always apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: workstations become: true tasks: - name: install unzip package: name: unzip - name: install terraform unarchive: src: https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip dest: /usr/local/bin remote_src: yes mode: 0755 owner: root group: root - hosts: web_servers become: true tasks: - name: install httpd package (CentOS) tags: apache,centos,httpd dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: start and enable httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: started when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) tags: apache,apache2,ubuntu apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: copy html file for site tags: apache,apache,apache2,httpd copy: src: default_site.html dest: /var/www/html/index.html owner: root group: root mode: 0644 - hosts: db_servers become: true tasks: - name: install mariadb server package (CentOS) tags: centos,db,mariadb dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server tags: db,mariadb,ubuntu apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: file_servers tags: samba become: true tasks: - name: install samba package tags: samba package: name: samba state: latest site.yml (вторая версия) # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) tags: always dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) tags: always apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: workstations become: true tasks: - name: install unzip package: name: unzip - name: install terraform unarchive: src: https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip dest: /usr/local/bin remote_src: yes mode: 0755 owner: root group: root - hosts: web_servers become: true tasks: - name: install httpd package (CentOS) tags: apache,centos,httpd dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: start and enable httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: started enabled: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) tags: apache,apache2,ubuntu apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: copy html file for site tags: apache,apache,apache2,httpd copy: src: default_site.html dest: /var/www/html/index.html owner: root group: root mode: 0644 - hosts: db_servers become: true tasks: - name: install mariadb server package (CentOS) tags: centos,db,mariadb dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server tags: db,mariadb,ubuntu apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: file_servers tags: samba become: true tasks: - name: install samba package tags: samba package: name: samba state: latest site.yml # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) tags: always dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) tags: always apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: workstations become: true tasks: - name: install unzip package: name: unzip - name: install terraform unarchive: src: https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip dest: /usr/local/bin remote_src: yes mode: 0755 owner: root group: root - hosts: web_servers become: true tasks: - name: install httpd package (CentOS) tags: apache,centos,httpd dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: start and enable httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: started enabled: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) tags: apache,apache2,ubuntu apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: change e-mail address for admin tags: apache,centos,httpd lineinfile: path: /etc/httpd/conf/httpd.conf regexp: \u0026#39;^ServerAdmin\u0026#39; line: ServerAdmin somebody@somewhere.net when: ansible_distribution == \u0026#34;CentOS\u0026#34; register: httpd - name: restart httpd (CentOS) tags: apache,centos,httpd service: name: httpd state: restarted when: httpd.changed - name: copy html file for site tags: apache,apache,apache2,httpd copy: src: default_site.html dest: /var/www/html/index.html owner: root group: root mode: 0644 - hosts: db_servers become: true tasks: - name: install mariadb server package (CentOS) tags: centos,db,mariadb dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server tags: db,mariadb,ubuntu apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: file_servers tags: samba become: true tasks: - name: install samba package tags: samba package: name: samba state: latest Оригинал\n","date":"4 февраля 2025","externalUrl":null,"permalink":"/manuals/ansible-part12-managing-services/","section":"Manuals","summary":"","title":"Курс Ansible - урок 12 - Управление службами","type":"manuals"},{"content":" Курс Ansible - урок 10 - Теги # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач. В десятом видео мы узнаем, как добавлять теги к нашим сценариям, чтобы было проще ориентироваться в конкретных задачах, когда мы не хотим каждый раз запускать весь сценарий.\nsite.yml (с добавленными тегами) # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) tags: always dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) tags: always apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: web_servers become: true tasks: - name: install httpd package (CentOS) tags: apache,centos,httpd dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) tags: apache,apache2,ubuntu apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: db_servers become: true tasks: - name: install mariadb server package (CentOS) tags: centos,db,mariadb dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server tags: db,mariadb,ubuntu apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: file_servers tags: samba become: true tasks: - name: install samba package tags: samba package: name: samba state: latest Список доступных тегов в плейбуке # ansible-playbook --list-tags site_with_tags.yml Примеры запуска плейбука, но нацеленного на определенные теги # ansible-playbook --tags db --ask-become-pass site_with_tags.yml ansible-playbook --tags centos --ask-become-pass site_with_tags.yml ansible-playbook --tags apache --ask-become-pass site_with_tags.yml Оригинал\n","date":"30 января 2025","externalUrl":null,"permalink":"/manuals/ansible-part10-tags/","section":"Manuals","summary":"","title":"Курс Ansible - урок 10 - Теги","type":"manuals"},{"content":" Курс Ansible - урок 9 - Нацеливаемся на конкретные узлы # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач. В видео № 9 мы разделим файл инвентаризации на группы и рассмотрим, как запускать задачи на узлах в зависимости от их группы.\ninstall_apache.yml # --- - hosts: all become: true tasks: - name: install apache and php for Ubuntu servers apt: name: - apache2 - libapache2-mod-php state: latest update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: install apache and php for CentOS servers dnf: name: - httpd - php state: latest update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; инвентарь (обновление с группами) # [web_servers] 172.16.250.132 172.16.250.248 [db_servers] 172.16.250.133 [file_servers] 172.16.250.134 site.yml # --- - hosts: all become: true tasks: - name: install updates (CentOS) dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: web_servers become: true tasks: - name: install apache and php for Ubuntu servers apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: install apache and php for CentOS servers dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; site.yml (вторая версия) # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: web_servers become: true tasks: - name: install apache2 package apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: install httpd package dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; site.yml (третья версия) # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: web_servers become: true tasks: - name: install httpd package (CentOS) dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: db_servers become: true tasks: - name: install httpd package (CentOS) dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; site.yml (четвертая версия) # --- - hosts: all become: true pre_tasks: - name: install updates (CentOS) dnf: update_only: yes update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install updates (Ubuntu) apt: upgrade: dist update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: web_servers become: true tasks: - name: install httpd package (CentOS) dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache2 package (Ubuntu) apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: db_servers become: true tasks: - name: install mariadb server package (CentOS) dnf: name: mariadb state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install mariadb server apt: name: mariadb-server state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - hosts: file_servers become: true tasks: - name: install samba package package: name: samba state: latest Оригинал\n","date":"29 января 2025","externalUrl":null,"permalink":"/manuals/ansible-part09-targeting_nodes/","section":"Manuals","summary":"","title":"Курс Ansible - урок 9 - Нацеливаемся на конкретные узлы","type":"manuals"},{"content":" Курс Ansible - урок 8 - Улучшаем плейбук # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач. В видео № 8 мы рассмотрим несколько способов очистки и объединения плейбуков, с которыми мы работали до сих пор.\ninstall_apache.yml (предыдущая версия) # --- - hosts: all become: true tasks: - name: update repository index for Ubuntu apt: update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: install apache2 package for Ubuntu apt: name: apache2 state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: add php support for apache for Ubuntu apt: name: libapache2-mod-php for CentOS state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: update repository index for CentOS dnf: update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install httpd package for CentOS dnf: name: httpd state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: add php support for apache for CentOS dnf: name: php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; install_apache.yml (сокращенный) # --- - hosts: all become: true tasks: - name: update repository index apt: update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: install apache2 and php packages for Ubuntu apt: name: - apache2 - libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: update repository index dnf: update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install apache and php packages for CentOS dnf: name: - httpd - php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; install_apache.yml (далее сокращенный) # --- - hosts: all become: true tasks: - name: install apache2 package apt: name: - apache2 - libapache2-mod-php state: latest update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: install httpd package dnf: name: - httpd - php state: latest update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; install_apache.yml (еще более сокращенный) # --- - hosts: all become: true tasks: - name: install apache and php package: name: - \u0026#34;Template:Apache package\u0026#34; - \u0026#34;Template:Php package\u0026#34; state: latest update_cache: yes файл инвентаризации (с добавлением переменных хоста) # 172.16.250.132 apache_package=apache2 php_package=libapache2-mod-php 172.16.250.133 apache_package=apache2 php_package=libapache2-mod-php 172.16.250.134 apache_package=apache2 php_package=libapache2-mod-php 172.16.250.248 apache_package=httpd php_package=php_package Оригинал\n","date":"28 января 2025","externalUrl":null,"permalink":"/manuals/ansible-part08-improving_playbook/","section":"Manuals","summary":"","title":"Курс Ansible - урок 8 - Улучшаем плейбук","type":"manuals"},{"content":" Курс Ansible - урок 7 - Условие \u0026ldquo;when\u0026rdquo; # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач. В видео № 7 мы рассмотрим, как различать наши плейбуки.\ninstall_apache.yml # --- - hosts: all become: true tasks: - name: update repository index apt: update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: install apache2 package apt: name: apache2 state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: add php support for apache apt: name: libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; Собирайте данные, ограничиваясь при этом одним хостом # ansible all -m gather_facts --limit 172.16.250.248 install_apache.yml (обновлен для включения centos) # --- - hosts: all become: true tasks: - name: update repository index apt: update_cache: yes when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: install apache2 package apt: name: apache2 state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: add php support for apache apt: name: libapache2-mod-php state: latest when: ansible_distribution == \u0026#34;Ubuntu\u0026#34; - name: update repository index dnf: update_cache: yes when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: install httpd package dnf: name: httpd state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; - name: add php support for apache dnf: name: php state: latest when: ansible_distribution == \u0026#34;CentOS\u0026#34; Оригинал\n","date":"27 января 2025","externalUrl":null,"permalink":"/manuals/ansible-part07-when-conditional/","section":"Manuals","summary":"","title":"Курс Ansible - урок 7 - Условие \"when\"","type":"manuals"},{"content":" Курс Ansible - урок 6 - Пишем наш первый плейбук # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что вам нужно знать, чтобы использовать Ansible для повседневных административных задач. В видео № 6 мы начнём писать сценарии, с помощью которых будем использовать Ansible в дальнейшем.\ninstall_apache.yml # --- - hosts: all become: true tasks: - name: install apache2 package apt: name: apache2 Запускаем playbook # ansible-playbook --ask-become-pass install_apache.yml install_apache.yml (вторая версия) # --- - hosts: all become: true tasks: - name: update repository index apt: update_cache: yes - name: install apache2 package apt: name: apache2 install_apache.yml (третья версия) # --- - hosts: all become: true tasks: - name: update repository index apt: update_cache: yes - name: install apache2 package apt: name: apache2 - name: add php support for apache apt: name: libapache2-mod-php install_apache.yml (четвертая версия) # --- - hosts: all become: true tasks: - name: update repository index apt: update_cache: yes - name: install apache2 package apt: name: apache2 state: latest - name: add php support for apache apt: name: libapache2-mod-php state: latest remove_apache.yml # --- - hosts: all become: true tasks: - name: remove apache2 package apt: name: apache2 state: absent - name: remove php support for apache apt: name: libapache2-mod-php state: absent Оригинал\n","date":"24 января 2025","externalUrl":null,"permalink":"/manuals/ansible-part06-writing_first_playbook/","section":"Manuals","summary":"","title":"Курс Ansible - урок 6 - Пишем наш первый плейбук","type":"manuals"},{"content":" Курс Ansible - урок 5 - Запуск команд с повышенными правами # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач. В пятом видео мы рассмотрим дополнительные специальные команды, но на этот раз команды, которые вносят изменения.\nУкажите ansible использовать sudo # ansible all -m apt -a update_cache=true --become --ask-become-pass Установите пакет через модуль apt # ansible all -m apt -a name=vim-nox --become --ask-become-pass Установите пакет с помощью модуля apt, а также убедитесь, что это последняя доступная версия # ansible all -m apt -a \u0026#34;name=snapd state=latest\u0026#34; --become --ask-become-pass Обновите все доступные обновления пакета # ansible all -m apt -a upgrade=dist --become --ask-become-pass Оригинал\n","date":"23 января 2025","externalUrl":null,"permalink":"/manuals/ansible-part05-elevated_commands/","section":"Manuals","summary":"","title":"Курс Ansible - урок 5 - Запуск команд с повышенными правами","type":"manuals"},{"content":" Курс Ansible - урок 4 - Выполнение специальных команд # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач. В четвёртом эпизоде мы устанавливаем Ansible и используем его для выполнения некоторых специальных команд.\nУстановить ansible # sudo apt update sudo apt install ansible Создайте файл инвентаризации (добавьте IP-адрес каждого сервера в отдельную строку) # 172.16.250.132 172.16.250.133 172.16.250.134 Добавьте файл инвентаризации в систему управления версиями # git add inventory Зафиксируйте изменения # git commit -m \u0026#34;first version of the inventory file, added three hosts.\u0026#34; Отправить коммит на Github # git push origin master Проверьте работу ansible # ansible all --key-file ~/.ssh/ansible -i inventory -m ping Создайте файл конфигурации ansible # nano ansible.cfg [defaults] inventory = inventory private_key_file = ~/.ssh/ansible Теперь команда ansible может быть упрощена # ansible all -m ping Перечислите все хосты в инвентаре # ansible all --list-hosts Соберите данные со всех хостов # ansible all -m gather_facts Соберите данные, но ограничьте их только одним хостом # ansible all -m gather_facts --limit 172.16.250.132 Оригинал\n","date":"22 января 2025","externalUrl":null,"permalink":"/manuals/ansible-part04-adhoc_commands/","section":"Manuals","summary":"","title":"Курс Ansible - урок 4 - Выполнение специальных команд","type":"manuals"},{"content":" Курс Ansible - урок 3 - Настройка репозитория Git # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что вам нужно знать, чтобы использовать Ansible для повседневных административных задач. В третьей части мы рассмотрим ещё одну базовую концепцию — Git. Все, кто эффективно внедряет автоматизацию, используют Git, и в этом видео мы рассмотрим создание репозитория и отправку изменений.\nПроверьте, установлен ли git # which git Установить git # sudo apt update sudo apt install git Создайте конфигурацию пользователя для git # git config --global user.name \u0026#34;First Last\u0026#34; git config --global user.email \u0026#34;somebody@somewhere.net\u0026#34; Проверьте состояние вашего репозитория git # git status Добавьте файл README.md (после внесения изменений), чтобы он был включён в следующий коммит git # git add README.md Настройте файл README.md для включения в коммит # git commit -m «Обновлённый файл README, первая фиксация» Отправьте коммит на Github # git push origin master Оригинал\n","date":"21 января 2025","externalUrl":null,"permalink":"/manuals/ansible-part03-setting_git/","section":"Manuals","summary":"","title":"Курс Ansible - урок 3 - Настройка репозитория Git","type":"manuals"},{"content":" Курс Ansible - урок 2 - Обзор и настройка SSH # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что вам нужно знать, чтобы использовать Ansible для повседневных административных задач. Во втором эпизоде мы рассмотрим некоторые базовые знания, в частности OpenSSH, который необходим для работы Ansible.\nСгенерировать ssh-ключ # ssh-keygen -t ed25519 -C \u0026#34;jay default\u0026#34; Скопируйте ssh-ключ на сервер (ы) # ssh-copy-id -i ~/.ssh/id_ed25519.pub \u0026lt;IP Adderss\u0026gt; Сгенерируйте ssh-ключ, который будет использоваться специально для Ansible # ssh-keygen -t ed25519 -C \u0026#34;ansible\u0026#34; Скопируйте ssh-ключ на сервер (ы) # ssh-copy-id -i ~/.ssh/ansible.pub Используйте SSH-ключ для подключения к серверу # ssh -i .ssh/\u0026lt;key_name\u0026gt; \u0026lt;IP Address\u0026gt; Чтобы кэшировать кодовую фразу для нашего сеанса, мы можем использовать ssh-агент # eval $(ssh-agent) ssh-add Вот псевдоним, который вы можете добавить в свой .bashrc, чтобы упростить работу # alias ssha=\u0026#39;eval $(ssh-agent) \u0026amp;\u0026amp; ssh-add\u0026#39; Оригинал\n","date":"20 января 2025","externalUrl":null,"permalink":"/manuals/ansible-part02-ssh_overview/","section":"Manuals","summary":"","title":"Курс Ansible - урок 2 - Обзор и настройка SSH","type":"manuals"},{"content":" Курс Ansible - урок 1 - Введение # Ansible — это невероятная утилита для управления конфигурацией и подготовки к работе, которая позволяет автоматизировать все процессы. В этой серии вы узнаете всё, что нужно знать, чтобы использовать Ansible для повседневных административных задач. В первом видео этой серии мы рассмотрим общую тему и то, чего ожидать.\nОригинал\n","date":"19 января 2025","externalUrl":null,"permalink":"/manuals/ansible-part01-introduction/","section":"Manuals","summary":"","title":"Курс Ansible - урок 1 - Введение","type":"manuals"},{"content":"Купили VPS, получили от провайдера пароль root и ip-адрес. А дальше что?\nПервым делом # Заходим на сервер первый и последний раз под рутом\nssh root@ip Обновляемся\napt update \u0026amp;\u0026amp; apt upgrade -y Устанавливаем минимально нужное ПО\napt install sudo ufw nano wget fish neofetch ncdu btop fail2ban Создаем пользователя\nuseradd -m -G sudo -s /usr/bin/fish odin \u0026amp;\u0026amp; passwd odin Здесь я для пользователя указываю оболочку fish-shell, к которой я привык Проверяем сетевые соединения\nss -tulpan На этом этапе ожидаем увидеть только наше соединение на 22 порту. Если соединений больше - это повод уже на этом этапе озаботится что именно и куда подключается с нашего девственного сервера. Сделаем сервер чуть безопаснее # Отключаем возможность логиниться под рутом\nnano /etc/ssh/sshd_config Меняем значение параметра PermitRootLogin указываем no\nНастроим файервол, разрешив любые подключения только со своего ip-адреса\nufw allow from 1.2.3.4 1.2.3.4 - ваш статический ip, с которого подключаетесь к VPS {: .prompt-info } Активируем файервол\nufw enable И проверяем\nufw status verbose Должны увидеть статус active и соединения разрешены любые Anywhere с нашего ip-адреса 1.2.3.4\nStatus: active Logging: on (low) Default: deny (incoming), allow (outgoing), deny (routed) New profiles: skip To Action From -- ------ ---- Anywhere ALLOW IN 1.2.3.4 Завершаем сессию под рутом exit или Cntrl+d и заходим под созданным ранее пользователем\nssh odin@ip Добавим авторизацию по ключам\nmkdir .ssh nano .ssh/authorized_keys Добавляем в файл authorized_keys наши публичные ключи\nЗавершаем сессию exit или Cntrl+d и заходим еще раз\nssh odin@ip В этот раз вход должен пройти без запроса пароля\nИнтереса ради можем проверить, кто пытался зайти на наш сервер, пока мы его настраивали sudo grep \u0026#34;Failed password\u0026#34; /var/log/auth.log | cut -d \u0026#39; \u0026#39; -f 9 | sort | uniq -c | sort -rh sudo grep \u0026#34;Invalid user\u0026#34; /var/log/auth.log | cut -d \u0026#39; \u0026#39; -f 9 | sort | uniq -c | sort -rh Перезагружаемся\nsudo reboot Базовая настройка выполнена\nВторостепенные настройки # Настройка UTF-8 локали\nsudo dpkg-reconfigure locales export LC_ALL=en_US.UTF-8 export LANG=en_US.UTF-8 export LANGUAGE=en_US.UTF-8 Меняем имя нашего сервера\nsudo nano /etc/hostname Настраиваем часовой пояс\nsudo timedatectl set-timezone Europe/Moscow Еще у нас установлены дополнительные утилиты, у каждой есть свои настройки, но можно пользоваться и параметрами по-умолчанию\nneofetch или более современный fastfetch - отображение системный информации btop - монитор системных ресурсов fail2ban - автобан тех ip-адресов, кто пытается подбирать пароли ncdu - отображение размера папок Итог # создали пользователя и отключили root установили и настроили файервол, разрешив подключения пока только со своего ip-адреса настроили авторизацию по ключам настроили локаль, имя сервера и часовой пояс установили дополнительные утилиты ","date":"9 сентября 2024","externalUrl":null,"permalink":"/posts/2024-09-09-5-minutes-vps/","section":"Статьи","summary":"","title":"Первые 5 минут на сервере","type":"posts"},{"content":"","date":"12 марта 2024","externalUrl":null,"permalink":"/tags/selfhosted/","section":"Tags","summary":"","title":"Selfhosted","type":"tags"},{"content":"","date":"12 марта 2024","externalUrl":null,"permalink":"/tags/tv/","section":"Tags","summary":"","title":"Tv","type":"tags"},{"content":"","date":"12 марта 2024","externalUrl":null,"permalink":"/categories/tv/","section":"Categories","summary":"","title":"TV","type":"categories"},{"content":" Дано # Что такое торренты знают все, как их смотреть на андроид приставках знают многие. Расскажу как смотреть торренты на AppleTV без предварительного скачивания, регистрации и смс.\nПриступим # 1 - TorrServer # Нам нужен кеширующий торрент сервер, который будет докачивать файл в то время, как мы его уже смотрим. В этой роли выступает TorrServer Запускаем его на отдельном сервере/компьютере с помощью docker\ndocker run --rm -d -e TS_PORT=5665 -e TS_DONTKILL=1 -e TS_HTTPAUTH=1 -e TS_RDB=1 -e TS_CONF_PATH=/opt/ts/config -e TS_LOG_PATH=/opt/ts/log -e TS_TORR_DIR=/opt/ts/torrents --name torrserver -v ~/ts:/opt/ts -p 5665:5665 ghcr.io/yourok/torrserver:latest или docker compose\n# docker-compose.yml version: \u0026#39;3.3\u0026#39; services: torrserver: image: ghcr.io/yourok/torrserver container_name: torrserver environment: - TS_PORT=5665 - TS_DONTKILL=1 - TS_HTTPAUTH=0 - TS_CONF_PATH=/opt/ts/config - TS_TORR_DIR=/opt/ts/torrents volumes: - \u0026#39;./CACHE:/opt/ts/torrents\u0026#39; - \u0026#39;./CONFIG:/opt/ts/config\u0026#39; ports: - \u0026#39;5665:5665\u0026#39; restart: unless-stopped Здесь CONFIG директория, где сохраняется файл конфигурации (создается по-умолчанию, редактировать его необязательно), CACHE - папка, куда будут временно скачиваться торренты, пока мы их смотрим.\n2 - LAMPA # Приложение для AppleTV называется LAMPA (можно смореть и в браузере на компьютере)\nПриложения на данный момент нет в AppStore и вряд ли будет. Поэтому качаем его через TestFlight. TestFlight должен быть установлен и на телефоне и на AppleTV, оба должны быть привязаны к одному Apple ID.\nСначала на телефоне открываем ссылку на тестирование LAMPA Открыть TestFlight на Apple TV, будет виден список из приложений, где вы участвуете в тестировании Принять, Установить, открыть. 3 - Настройки # Сразу после установки LAMPA на Apple TV максимум, что она нам предложит - это посмотреть трейлеры к фильмам, потому что функцию торрентов из нее вырезали (видимо иначе не пройдет модерацию). Возвращаем функционал просмотра торрентов, для этого установим плагин - Настройки -\u0026gt; Расширения -\u0026gt; Добавить плагин.\nПосле этого в настройках появятся два новых пункта Парсер и Torrserver. В меню Torrserver основная ссылка указываем адрес компьютера и порт, где у нас запущен torrserer - что-то вроде http://192.168.88.10:5665\nВ меню Парсер указываем - тип парсера jackett, ссылка jacred.xyz, все остальные поля пропускаем.\nА еще для воспроизведения торрентов нам нужен сторонний плеер Infuse или VLC, скачиваем их в App Store и указываем в лампе Настройка -\u0026gt; Плеер -\u0026gt; Тип плеера для торрентов.\nРезультат # При нажатии Смотреть появится опция торрент и дальше выбираем из списка, какой вариант для просмотра нам больше подходит\nИтог # Не самое простое решение, не самое удобное - зато абсолютно бесплатное\n","date":"12 марта 2024","externalUrl":null,"permalink":"/posts/2024-03-12-torrserver/","section":"Статьи","summary":"","title":"Смотрим торренты на AppleTV","type":"posts"},{"content":"","date":"1 ноября 2023","externalUrl":null,"permalink":"/categories/linux/","section":"Categories","summary":"","title":"Linux","type":"categories"},{"content":" Дано # ТВ приставка, купленная когда-то для просмотра 4k фильмов, не подошла она по разным причинам и отправилась в чулан на несколько лет.\nПриступим # По надписи на приставке Mini M8S находится информация, что приставка на чипсете S905. А вот при загрузке андроида на экране отображается эмблема S905X.\nСкачиваем образ armbian с гитхаба для чипа S905X и записываем на microSD c помощью программы Balena На microSD появится два раздела: BOOT и ROOTFS, переходим в раздел BOOT Находим файл, подходящий для нашей архитектуры u-boot-s905x-s912.bin и переименовываем его в u-boot.ext В папке extlinux проверяем файл extlinux.conf. В моем случае редактировать содержимое файла не пришлось, но сам файл назывался неверно - extlinux.conf.bak Вытаскиваем флешку из компьютера и вставляем в ТВ приставку, вынимаем шнур питания, зубочисткой зажимаем кнопку reset и вставляем шнур питания обратно ТА-ДАА # Загрузилась вариация Ubuntu Lunar для arm-процессоров.\nРаботает наш линукс пока еще с флешкарты, сколько карточка проживет в таком режиме неизвестно, поэтому установим систему на внутреннюю память приставки.\nВНИМАНИЕ: андроид на приставке вы таким образом уничтожите, как и все содержимое на внутренем носителе, обратного пути не будет. {: .prompt-danger }\nsudo armbian-install Первая попытка заканчивается ошибкой - система не может найти файл u-boot-s905x-s912.bin. Естественно - мы же ранее переименовали его в u-boot.ext. Поэтому копируем его с оригинальным названием\nsudo cp /boot/u-boot.ext /boot/u-boot-s905x-s912.bin И снова запускаем команду armbian-install. В этот раз установка системы проходит без ошибок и предлагает перезагрузиться и вытащить флешку. Так и делаем.\nЦель # После загрузки и проверки, что линукс работает и грузится без проблем, приступим собственно к каким-нибудь полезным вещам.\nAdGuard # В предыдущей статье я пробовал установить Adguard на роутер Mikrotik, получилось не очень рабочее решение. Попробуем теперь приставку превратить в dns-сервер Adguard.\nAdGuardHome устанавливается достаточно просто, автоматическим скриптом.\nwget --no-verbose -O - https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v В роутере необходимо указать, что для локальной сети в качестве dns-сервера будет использоваться наша приставка.\nПриставке потребуется статический адрес {: .prompt-tip }\nВ Микротике это IP -\u0026gt; DHCP server -\u0026gt; Networks -\u0026gt; DNS Servers\nDocker и умный дом # Установим докер и сервер для умного дома. ПО на armbian можно устанавливать с помощью armbian-config\nНО Openhab таким образом не установился, докер установился, но отказался запускаться. Поэтому ставим все ручками.\nИнструкция для докер.\nИнструкция для Openhab.\nВ дополнение еще поставил:\nHomebridge для интеграции openhab и умного дома Apple Portainer для удобного управления докер-контейнерами Watchtower чтоб контейнеры сами обновлялись Uptime Kuma - мониторит состояние моих внутренних/внешних сервисов и шлет мне сообщения в телеграм, если что-то сломалось Все вместе потребляет примерно 60% оперативной памяти Итог # Сервер из ТВ-приставки - отличная замена решениям на малинках/апельсинках.\n","date":"1 ноября 2023","externalUrl":null,"permalink":"/posts/2023-11-01-tvbox/","section":"Статьи","summary":"","title":"Превращаем андроид ТВ приставку в сервер","type":"posts"},{"content":" Mikrotik с версии RouterOS 7.5 поддерживает контейнеры докер # Запустим что-нибудь интересное, например AdguardHome\nДля начала скачиваем файл Extra packages для своей архитектуры с сайта Mikrotik.\nРазархивируем и файл container-7.X.npk закидываем на роутер в меню Files.\nПерезагружаем роутер.\nФункционал контейнеров добавлен, но еще надо его активировать.\nДля этого в терминале роутера выполняем команду\n/system/device-mode/update container=yes После этой команды в течении 5 минут необходимо выполнить cold reboot роутера. Сold reboot - это выдернуть шнур питания из розетки или нажать кнопку reset на роутере. В моем случае выдергивание из розетки не помогло и пришлось вытаскивать роутер из стойки и жать кнопку reset.\nПоявится меню container:\nВ линуксе докер сам создает необходимую себе инфраструктуру: сеть, интерфейсы, адреса, правила файрвола. Здесь же придется все прописывать вручную..\nСоздадим bridge для контейнеров\n/interface/bridge/add name=containers Назначим ему внутренний адрес\n/ip/address/add address=172.17.0.1/24 interface=containers Создадим виртуальный сетевой интерфейс для нашего будущего контейнера и назначим ему адрес 172.17.0.2\n/interface/veth/add name=veth1 address=172.17.0.2/24 gateway=172.17.0.1 Добавим интерфейс будущего контейнера в созданный bridge\n/interface/bridge/port add bridge=containers interface=veth1 Для каждого контейнера надо создавать свой интерфейс veth c новым адресом и добавлять их в bridge\nВнутренней памяти роутера скорее всего окажется недостаточно для запуска большинства контейнеров, поэтому рекомендую использовать флешку или внешний диск. Их необходимо отформатировать самостоятельно, либо через меню System -\u0026gt; Disk -\u0026gt; Format Drive Переходим к настройке контейнера Container -\u0026gt; Config\n/container/config/set registry-url=https://registry-1.docker.io tmpdir=usb1-part1/pull Здесь https://registry-1.docker.io реестр откуда будут скачиваться образы контейнеров, usb1-part1/pull - директория на флешке куда они будут скачиваться.\nДля контейнера AdGuardHome смонтируем с флешки две папки work и conf\n/container/mounts/add name=adguard-conf src=usb1-part1/adguard/conf dst=/opt/adguardhome/conf /container/mounts/add name=adguard-work src=usb1-part1/adguard/work dst=/opt/adguardhome/work Запускаем сам контейнер AdGuardHome\n/container/add remote-image=adguard/adguardhome:latest interface=veth1 root-dir=usb1-part1/adguard/root mounts=adguard-conf,adguard-work Ждем когда контейнер загрузится и получит статус stopped, после этого запускаем его кнопкой Start или командой\n/container/start 0 Осталось перебросить порт с роутера на вирутальный порт нашего контейнера\n/ip firewall nat add action=dst-nat chain=dstnat dst-address=192.168.88.1 dst-port=3000 protocol=tcp to-addresses=172.17.0.2 to-ports=3000 Теперь зайдя по адресу 192.168.88.1:3000 мы увидим настройку AdGuardHome\nСоветую порт, на которм будет доступна панель управления AdGuardHome оставить 3000, чтобы не переписывать предыдущее правило {: .prompt-tip }\nИ укажем роутеру использовать AdGuardHome в качестве DNS-сервера\n/ip dns set servers=172.17.0.2 Плюсы # С помощью контейнеров можем бесконечно (в пределах аппаратных ресурсов) расширять функционал роутера Можем вырезать рекламу с помощью черных списков AdGuardHome Минусы # В AdGuardHome мы увидим только одного клиента, собственно наш роутер с адресом 172.17.0.1, не получится настраивать доступ к сайтам для отдельных клиентов сети\nПри перезагрузке не факт, что заработает..\nУ меня при отключении электричества, контейнеры отказались запускаться. Первый раз получилось пересоздать их вручную, второй раз - не получилось ничего. Учитывая пункт 1, решать пункт 2 я не стал.\nХорошая попытка Mikrotik - но нет # Технология докер контейнеров на RouterOS для меня лично отправляется на полку. Может когда-нибудь попробую еще раз. В OPNsense к примеру, функционал AdGuardHome добавляется как плагин. Все, оно сразу начинает работать, никаких cold reboot или ресетов, никаких бриджей и виртуальных интерфейсов, реестров и точек монтирования, и AdGuardHome видит всех внутренних клиентов сети.\n","date":"23 октября 2023","externalUrl":null,"permalink":"/posts/2023-10-23-mikrotikdocker/","section":"Статьи","summary":"","title":"Запускаем контейнеры докер на роутере Mikrotik","type":"posts"},{"content":" Приведенная схема используется мною исключительно для доступа к ресурсам, владельцы которых ограничили доступ к информации с российских IP. А таких технических иностранных ресурсов оказалось достаточное количество. Что там блокирует Роскомнадзор мне все равно, я и до его (Роскомнадзора) блокировок их не посещал. {: .prompt-tip }\nС понедельника иностранные VPN сервисы блокируются в мобильных сетях России # Блокируется VPN доступ в мобильной сети до иностранных серверов Работает VPN доступ в мобильной сети к российским серверам VPN работает в проводных сетях У меня есть статья по настройке wireguard сервера.\nflowchart LR a1[Клиент] ---\u003e|VPN|b3[IP в зоне EU/US] subgraph node2[Сервер] b3 end b3 ---\u003eC((Весь остальной мир IP)) Теперь эта схема в мобильной сети не работает.\nЧто же делать? # Что может быть лучше одного VPN сервера? Правильно - два VPN сервера. Первый VPN сервер нужен с российским IP адресом.\nНо просто ввести в схему еще один сервер скучно, поэтому получим максимум из этой ситуации - добавим маршрутизацию по geoip.\nflowchart LR a1[Клиент] ---\u003e|WG|b1[IP в зоне RU] b1 ---\u003eB((Российские IP)) subgraph node2[Сервер 2] b3 end subgraph node1[Сервер 1] b1 end b1 ---\u003e|WG| b3[IP в зоне EU/US] b3 ---\u003eC((Весь остальной мир IP)) Подключение мобильного клиента к серверу 1 работает, т.к. он находится на территории России Подключение сервера 1 к серверу 2 работает - используется проводная сеть За счет использования гео-маршрутизации на сервере 1: российские сервисы открываются через российский сервер, все остальные ресурсы отправляются через иностранный сервер Теперь нет необходимости включать/отключать VPN # Реализация # Для клиента и сервера 2 ничего не меняется - можно использовать мою статью по настройке wireguard сервера.\nА вот для сервера 1 потребуется специализированное ПО - я рекомендую OPNsense. Как установить OPNsense на сервер, базовая настройка, настройка wireguard и geoip маршрутизации - тема очень объемная и выходит за рамки данной обзорной статьи.\n","date":"10 августа 2023","externalUrl":null,"permalink":"/posts/2023-08-10-forbidden-vpn/","section":"Статьи","summary":"","title":"ВПН не работает","type":"posts"},{"content":"Как же правильно определить «иностранность» почты? Если ваша почта оканчивается на @gmail.com все понятно. А если вы используете (как я) почту со своим/корпоративным доменом?\nКак вариант - можно определить путем анализа MX записи домена:\ndig +short example.ru MX А если используется частный почтовый сервис? Остается анализировать адрес IP на принадлежность его к российскому сегменту:\ngeoiplookup mail.example.ru Это будет на момент регистрации, но почту можно мигрировать на вражеский сервис/ip уже после регистрации. Как доказать что проверка была выполнена и избежать штрафов?\nСоставлять на каждого пользователя протокол о должной осмотрительности..\n","date":"1 августа 2023","externalUrl":null,"permalink":"/posts/2023-08-01-forbidden-email/","section":"Статьи","summary":"","title":"Запрещаем использование иностранной почты при регистрации на российских сайтах","type":"posts"},{"content":"","date":"24 апреля 2023","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":" В этой статье # Расскажу как настроить мониторинг хостов/кластера Proxmox и отображать в Grafana состояние запущенных контейнеров и виртуальных машин.\nКак запустить мониторинг Grafana/Prometheus за 5 минут можно прочитать в статье У тебя есть метрики? Дай!\nНастройки Proxmox VE # На хосте Proxmox создадим группу и пользователя для мониторинга и назначим соответствующую роль\npveum groupadd monitoring -comment \u0026#39;Monitoring group\u0026#39; pveum aclmod / -group monitoring -role PVEAuditor pveum useradd pve_exporter@pve pveum usermod pve_exporter@pve -group monitoring pveum passwd pve_exporter@pve Устанавливаем PVE-exporter # apt update apt install python3-pip pip3 install prometheus-pve-exporter Создаем файл настроек для PVE-exporter\nmkdir -p /usr/share/pve_exporter/ cat \u0026gt; /usr/share/pve_exporter/pve_exporter.yml \u0026lt;\u0026lt; EOF default: user: pve_exporter@pve password: пароль_пользователя_pve_exporter verify_ssl: false EOF Запускаем\n/usr/local/bin/pve_exporter /usr/share/pve_exporter/pve_exporter.yml Настройки Prometheus # В файл конфигурации Prometheus добавим\n- job_name: \u0026#39;pve\u0026#39; static_configs: - targets: - 192.168.1.1:9221 # Хост 1 Proxmox VE с PVE exporter - 192.168.1.2:9221 # Хост 2 Proxmox VE с PVE exporter metrics_path: /pve params: module: [default] Здесь можно указать сразу несколько хостов Proxmox для мониторинга. После изменения настроек перезапускаем сервис/контейнер Prometheus.\nНастройки Grafana # Импортируем дашборд №10347 в графану\nНаслаждаемся # Для отслеживания кластера - достаточно установить PVE-exporter на любой узел (для надежности можно и на все).\n","date":"24 апреля 2023","externalUrl":null,"permalink":"/posts/2023-04-24-pve-exporter/","section":"Статьи","summary":"","title":"Мониторинг Proxmox в Grafana","type":"posts"},{"content":"Если в домашней сети живет один Nextcloud или какой-нибудь медиа-сервер вроде Plex - обратный прокси вам не сдался. Когда домашних сервисов переваливает за десяток - помнить и вбивать их адреса и порты начинает уже утомлять.\nОбщая схема # flowchart LR a1[nextcloud.doma.net] ---\u003e B((Обратный прокси сервер)) a2[speedtest.doma.net] ---\u003e B((Обратный прокси сервер)) a3[plex.doma.net] ---\u003e B((Обратный прокси сервер)) B ---\u003e b1[192.168.88.10:8080] B ---\u003e b2[192.168.88.10:9000] B ---\u003e b3[192.168.88.20:3000] subgraph node1[Сервер 1] b1 b2 end subgraph node2[Сервер 2] b3 end Домустим у нас есть три сервиса:\nнаш внутренний или внешний домен doma.net Nextcloud - работает на сервере 192.168.88.20, порт 3000 Сервис для измерения скорости speedtest - работает на сервере 192.168.88.10, порт 8080 Медиа сервер Plex - - работает на сервере 192.168.88.10, порт 9000 Зачем нам обратный прокси, нельзя ли обойтись DNS? # Частично - можно. DNS позволит сделать связку имя ---\u0026gt; ip-адрес. В браузере все равно придется вбивать еще и порты:\nnextcloud.doma.net:3000 speedtest.doma.net:8080 plex.doma.net:9000 При этом, speedtest и plex запущены на одном сервере 192.168.88.10. Т.е фактически получится, что:\nplex будет доступен еще и по адресу speedtest.doma.net:9000 а в сервис speedtest можно зайти по адресу plex.doma.net:8080 Такая вот чехарда с DNS.\nИспользуя обратный прокси - мы эти три записи nextcloud.doma.net, speedtest.doma.net и plex.doma.net с помощью DNS направляем на адрес прокси сервера. А уже он, в зависимости от того, какой адрес ему поступил перенаправит запрос к нужному ip-адресу с указанным портом.\nПрактика # Запускаем прокси # Обратных прокси-серверов существует несколько - traefik, caddy, nging и др. Cамый интуитивно понятный, на мой взгляд - Nginx Proxy Manager. С ним и будем работать.\nДелать будем просто и быстро - в контейнерах докера. Для этого нам понадобится сам docker и docker compose.\nNginx Proxy Manager можно запускать на отдельном узле, а можно на одном узле вместе с остальными контейнерами. В нашем примере - допустим мы запускаем наш прокси на сервере 2, на одной машине вместе с nextcloud.\nСоздадим папку\nmkdir -p nginxproxy/{data,letsencrypt} cd nginxproxy Создадим файл запуска контейнера\nnano docker-compose.yml И вставим в него следующее содержимое\nversion: \u0026#39;3.8\u0026#39; services: app: image: \u0026#39;jc21/nginx-proxy-manager:latest\u0026#39; restart: unless-stopped ports: - \u0026#39;80:80\u0026#39; - \u0026#39;81:81\u0026#39; - \u0026#39;443:443\u0026#39; volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt jc21/nginx-proxy-manager:latest - образ Nginx Proxy Manager restart: unless-stopped - контейнер будет перезапускаться при сбоях, если только не остановлен вручную ports - открытые порты: 80 и 443 для web-трафика, порт 81 для администрирования самого Nginx Proxy Manager volumes - папки, смонтированные внутрь контейнера. data для разных внутренних данных и логов. А в папке letsencrypt будут храниться сертификаты, выпущенные одноименным сервисом. sudo docker compose up -d Ждем, когда скачается образ и запустится контейнер.\nВ этом примере Nginx Proxy Manager запускаем на сервере 2, поэтому вбиваем в браузер адрес этого сервера и добавляем порт администрирования http://192.168.88.20:81\nПервоначальные логин и пароль\nEmail: admin@example.com Password: changeme Настройки # Первым делом меняем логин/пароль администратора.\nВторым действием - сразу добавим шифрование. В 2023 большинство современных браузеров откажется открывать сайт без хоть какого-нибудь ssl сертификата.\nКак легко создать самоподписные сертификаты у меня есть небольшая статья.\nopenssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \\ -keyout domanet.key -out domanet.crt -subj \u0026#34;/CN=*.doma.net\u0026#34; \\ -addext \u0026#34;subjectAltName=DNS:*.doma.net\u0026#34; Это командой создается ключ и сертификат для всех поддоменов *.doma.net\nПерейдем в Nginx Proxy Manager на вкладку SSL Certificates ---\u0026gt; Add SSL Certificate ---\u0026gt; Custom и добавим только что сгенерированный сертификат и ключ к нему.\nТеперь к конфигурированию наших сервисов. Вкладка Hosts ---\u0026gt; Proxy Hosts ---\u0026gt; Add Proxy Host Вводим данные для Nextcloud На вкладке SSL - добавляем сертификат Жмем Save.\nРезультат # flowchart LR a1[nextcloud.doma.net] ---\u003e|DNS|B((Обратный прокси сервер)) subgraph node2[Сервер 2] b3 end B ---\u003e b3[192.168.88.20:3000] Теперь при вводе в строке браузера nextcloud.doma.net DNS перенаправит наш запрос на прокси сервер, а он уже перенаправит запрос на сервер 192.168.88.20:3000\nА еще # Можно генерировать сертификаты Let\u0026rsquo;s Encrypt, которые будут самообновляться Добавить дополнительную аутентификацию для сервисов Разрешать или запрещать доступ с определенных ip-адресов или сетей. Комментарии и вопросы в Телеграм или Дзен # ","date":"20 апреля 2023","externalUrl":null,"permalink":"/posts/2023-04-20-reverse-proxy/","section":"Статьи","summary":"","title":"Reverse proxy - обратный прокси, как и зачем","type":"posts"},{"content":" Дано # Я забыл пароль от внешнего зашифрованного диска, поэтому решил заново его инициализировать и написать памятку для себя будущего.\nсистема - Linux внешний диск - 1TB Теория # Обычный внешний диск мы сразу монтируем в какую-нибудь папку.\nsudo mount \u0026lt;устройство\u0026gt; \u0026lt;папка\u0026gt; В случае с шифрованием появляется промежуточный шаг - открытие контейнера. Сначала мы открываем внешний диск как контейнер, а уже потом этот контейнер монтируем как папку.\nПрактика # Устанавливаем утилиту cryptsetup, она есть практически в любом дистрибутиве.\nsudo apt install cryptsetup Смотрим под каким устройством появляется наш внешний диск\nls -lah /dev/sd* В моем случае /dev/sda - системный диск. А вот /dev/sdb - внешний, с ним и будем работать.\nШифрование уничтожит все данные на диске, важно сохранить всю информацию перед своими действиями и не перепутать устройство. Форматируем диск - указываем YES, что мы понимаем, что потеряем все данные на диске и два раза вводим парольную фразу.\nsudo cryptsetup luksFormat /dev/sdb Теперь откроем созданный контейнер (enc1TB - я назвал свое устройство)\nsudo cryptsetup open /dev/sdb enc1TB После этого наше уже расшифрованное устройство появится в директории /dev/mapper/\nСоздадим файловую систему\nsudo mkfs.ext4 /dev/mapper/enc1TB и смонтируем в директорию /mnt/1TB\nsudo mount /dev/mapper/enc1TB /mnt/1TB Проверим\ndf -h Можем работать с зашифрованным диском # Открыть диск\nsudo cryptsetup open /dev/sdb enc1TB sudo mount /dev/mapper/enc1TB /mnt/1TB Закрыть диск\nsudo umount /mnt/1TB sudo cryptsetup close enc1TB Памятка себе будущему # Андрей, не путай пароль sudo и passphrase диска. Я не забыл пароль от внешнего диска, я по невнимательности вбивал его, когда требовался пароль суперпользователя.\n","date":"2 апреля 2023","externalUrl":null,"permalink":"/posts/2023-04-02-cryptsetup/","section":"Статьи","summary":"","title":"Шифрование внешнего диска в Linux","type":"posts"},{"content":"","date":"27 марта 2023","externalUrl":null,"permalink":"/categories/%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%BD%D0%B5%D1%82/","section":"Categories","summary":"","title":"Интернет","type":"categories"},{"content":"","date":"27 марта 2023","externalUrl":null,"permalink":"/categories/%D1%81%D0%B5%D1%82%D0%B8/","section":"Categories","summary":"","title":"Сети","type":"categories"},{"content":"Теорию о пользе и важности мониторинга я опущу. Расскажу на практике: утечка трафика, которую я случайно обнаружил в январе - с монторингом не случилась бы в принципе. Мониторинг может рассылать уведомления о превышении каких-то параметров, а расследование причин в моем конкретном случае заняло бы пару минут, а не часов.\nTL:DR # Устанавливаем docker и docker compose. Скачиваем из репозитория Github конфигурационные файлы и изменяем их под свою сеть Выполняем комманду sudo docker compose up -d Устанавливаем на узлы Node exporter Подробнее # Общая схема # На устройстве, которое мы хотим мониторить, запускается сервис по сбору различной телеметрии (использование ЦП, памяти, сети и пр.) Эта информация предается на центральный узел, где собирается и хранится в базе данных Grafana берет данные из этой базы и рисует нам красивые графики Вариантов реализации много, я использовал два, а подробно разберу один:\nTelegraf ---\u0026gt; InfluxDB \u0026lt;--- Grafana В данном варианте, на клиенты устанавливается программа Telegraf - она собирает данные и отправляет на центральный узел. На центральном узле используется база данных InfluxDB, куда прилетают метрики от Telegraf\u0026rsquo;a, а также откуда берет данные графана для построения графиков. Старое и проверенное решение, но мониторить получиться компьютеры, на которые можно установить Telegraf.\nNode exporter \u0026lt;--- Prometheus \u0026lt;--- Grafana Более современное решение, его и разберу подробнее. В качестве сборщика данных используется сервис Prometheus. На узлах, которые мы собираемся мониторить - устанавливается программа Node exporter. Отличие от первого варианта Node exporter собирает , но не отправляет метрики на центральный узел. Prometheus сам опрашивает узлы, согласно настройке в конфиг-файле. Графана в первом и во втором случае работает одинаково - она берет данные из центральной базы.\nДокер # Делать будем все в контейнерах докера - самый простой и быстрый способ. Для этого нам понадобится сам docker и docker compose.\nСкачиваем конфигурационные файлы\nmkdir -p prometheus/{config,snmp_exporter} cd prometheus wget https://raw.githubusercontent.com/andrey-dvur/Scripts/main/Prometheus-Grafana/docker-compose.yml cd config wget https://raw.githubusercontent.com/andrey-dvur/Scripts/main/Prometheus-Grafana/config/prometheus.yml cd ../snmp_exporter wget https://raw.githubusercontent.com/prometheus/snmp_exporter/snmp.yml Что же мы скачали:\nфайл docker-compose.yml описывает какие докер контейнеры и с какими параметрами мы будем запускать. На начальном этапе это два контейнера - Prometheus и Grafana. в папку config скачался файл настроек prometheus.yml про файл snmp.yml в папке snmp_exporter расскажу чуть ниже Указываем в файле prometheus.yml узлы, которые будем мониторить\n######### HOMELAB ###################### - job_name: \u0026#39;node.A\u0026#39; # Имя узла static_configs: - targets: [\u0026#39;192.168.88.51:9100\u0026#39;] # hostname/ip-адрес узла, порт 9100 - стандартный для node-exporter - job_name: \u0026#39;node.B\u0026#39; static_configs: - targets: [\u0026#39;192.168.88.52:9100\u0026#39;] - job_name: \u0026#39;node.C\u0026#39; static_configs: - targets: [\u0026#39;192.168.88.53:9100\u0026#39;] Тут используется синтаксис YAML, поэтому, да, важно количество пробелов-отступов {: .prompt-info }\nПереходим в папку prometheus, где находится наш файл docker-compose.yml и запускаем наши контейнеры\nsudo docker compose up -d Ждем запуска обоих контейнеров, после этого открываем браузер, вбиваем ip-адрес нашего сервера, на ктором мы только что запустили контейнеры и порт графаны: 3000.\nБазовые настройки # http://ip-адрес:3000\nНачальные логин/пароль admin/admin\nВыбираем, откуда графана будет брать данные - наш источник Выбираем Prometheus Адреc нашего Prometheus указываем http://prometheus:9090\nдокер понимает написание адреса в таком виде {: .prompt-info }\nВнизу страницы нажимем Save \u0026amp; test Ожидаем увидеть: Дашборды # Источник данных настроили, перейдем к самой красоте - графикам.\nВообще Prometheus и сам может рисовать графики без графаны. Плюс графаны в данном случае - огромный выбор уже готовых наборов графиков (дашбордов), которые достаточно импортировать в свою конфигурацию.\nВыберем на сайте самый популярный дашборд Скопируем его ID Откроем импорт дашборда в нашей графане вставим скопированный ID, жмем Load Укажем источник данных для этого дашборда - наш, уже сконфигурированный Prometheus Prometheus и Grafana настроили, осталось на сам узел установить Node exporter # Для этого достаточно скачать бинарный файл, распаковать его и запустить. Все, node-exporter запущен, метрики собираются.\nРезультат достигнут # Node exporter \u0026lt;--- Prometheus \u0026lt;--- Grafana\nНа узле запущен Node exporter, с него собирает данные Prometheus, а уже из него берет информацию Grafana и отображает в импортированном дашборде.\nДополнения # Помимо Node exporter - у Prometheus огромный выбор и других экспортеров и интеграций. Можно мониторить базы данных, файловые системы, системы логирования и пр.\nНапример, в нашем файле docker-compose.yml есть сревис cadvisor, он собирает данные по докер контейнерам.\nРаскомментируем соответствующую часть\ncadvisor: image: gcr.io/cadvisor/cadvisor:latest container_name: cadvisor ports: - 8080:8080 volumes: - /:/rootfs:ro - /var/run:/var/run:rw - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro depends_on: - redis redis: image: redis:latest container_name: redis ports: - 6379:6379 Добавим настройки в конфиг-файл prometheus.yml\n- job_name: cadvisor scrape_interval: 5s static_configs: - targets: - cadvisor:8080 Перезапустим наши контейнеры\nsudo docker compose down sudo docker compose up -d В этот раз скачаются и запустятся еще два контейнера - cadvisor и redis\nИмпортируем дашборд для мониторинга контейнеров - например дашборд с ID 179\nТеперь мы можем наблюдать статистику по нашим контейнерам. Сколько каждый контейнер использует памяти или потребляет трафика.\nSNMP # Хорошо, а что делать с оборудованием на которое лишнего ничего не поставить, роутеры или старые Synology - используем SNMP.\nДля примера возьмем роутер Mirkotik, активируем в нем службу SNMP\nWebFig ---\u0026gt; IP ---\u0026gt; SNMP ---\u0026gt; Enabled\nВ нашем файле docker-compose.yml есть контейнер snmp_exporter, он будет собирать данные по SNMP и преобразовавать их в информацию, понятную Prometheus.\nРаскомментируем соответствующую часть\nsnmp_exporter: image: prom/snmp-exporter container_name: snmp_exporter restart: unless-stopped volumes: - ./snmp_exporter/:/etc/snmp_exporter/ ports: - \u0026#34;9116:9116\u0026#34; depends_on: - prometheus Первая буква в SNMP означает Simple - Простой, хотя ничего простого в этом протоколе нет. Каждый производитель использует свои наборы для управления объектами (MIB). В нашем случае мы будем использовать базу, которая подойдет для большинства случаев. Это тот самый файл snmp.yml в папке snmp_exporter, о котором я не рассказал в самом начале.\nДобавим настройки в конфиг-файл prometheus.yml\n- job_name: \u0026#39;mikrotik\u0026#39; static_configs: - targets: - 192.168.88.1 # mikrotik_ip metrics_path: /snmp params: module: [mikrotik] relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: snmp_exporter:9116 Перезапустим наши контейнеры\nsudo docker compose down sudo docker compose up -d Скачается и запустится еще контейнер - snmp_exporter\nИмпортируем дашборд для мониторинга контейнеров - мне понравился дашборд с ID 14420\nMikrotik # А еще # Можно создавать свои графики и настраивать уведомления о событиях. Процесс улучшайзинга графаны - бесконечен.\nЗаключение # Пока создавал статью наткнулся на курсы по grafana/prometheus - стоимостью 20 000 руб. Поздравляю, вы только что сэкономили эту сумму.\n","date":"27 марта 2023","externalUrl":null,"permalink":"/posts/2023-03-23-grafana/","section":"Статьи","summary":"","title":"У тебя есть метрики? Дай! Или мониторинг с помощью Prometheus и Grafana за 5 минут","type":"posts"},{"content":"SSL сертификаты позволяют шифровать данные, от пользователя к веб-сервису. В 2023 все данные в сети передаются зашифрованными, найти сайт с открытой передачей по http(без s) уже проблематично и о небезопасности такого сайта первым предупредит браузер.\nИспользовать или нет сертификаты (шифрование) внутри своей домашней сети вопрос дискуссионный # Я долгое время не заморачивался с ними, поскольку свои сервисы наружу в большой интернет не открываю, а внутри мне скрываться не от кого. Но однажды нашел обсуждение, где один из доводов в защиту сертификатов во внутренней сети было: \u0026ldquo;вы же даете свой пароль wifi гостям, друзьям - значит в вашей сети все-таки появляются недоверенные устройства, которые потенциально могут перехватывать открытый трафик\u0026rdquo;. А когда выяснил насколько легко создать и использовать ssl-сертификаты, вопрос их применения был решен.\nopenssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \\ -keyout example.key -out example.crt -subj \u0026#34;/CN=example.com\u0026#34; \\ -addext \u0026#34;subjectAltName=DNS:example.com\u0026#34; Собственно вот, одна команда и все. В результате для сайта example.com получим:\nexample.key - файл ключа example.crt - файл сертификата срок действия 10 лет параметры шифрования rsa:4096 -sha256, что в 2023 являются вменяемыми и достаточными (можете изменить на свое усмотрение) Можно завести в домашней сети свои Yandex.ru или Youtube.com. Но лучшей практикой - для всей домашней сети выбрать один домен, например doma.net, а все домашние сервисы заводить на его поддомены, т.е. youtuba.doma.net, homeassistanta.doma.net и др.\nopenssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \\ -keyout domanet.key -out domanet.crt -subj \u0026#34;/CN=*.doma.net\u0026#34; \\ -addext \u0026#34;subjectAltName=DNS:*.doma.net\u0026#34; Это командой создается ключ и сертификат для всех поддоменов *.doma.net\nИз минусов - все современные браузеры все равно будут ругаться на эти сертификаты и показывать \u0026ldquo;Соединение не защищено\u0026rdquo;, т.к. не могут проверить поставщика (а поставщик мы сами) сертификата. Но шифрование при этом работает.\nДа, можно использовать подтвержденные бесплатные сертификаты Lets encrypt, но для них надо:\nкупить доменное имя скорее всего вы захотите скрыть свои контактные данные, как владельца домена - а это услуга платная заплатить за перенаправление домена на DNS серверы заплатить за DNS-хостинг внести DNS-записи, для перенаправления доменного имени на свой IP-адрес. Заплатить за статический IP-адрес, либо настроить сервис DDNS Открыть и пробросить порты 80/443 до каждого сервиса, на который выдается сертификат Обновлять бесплатный сертификат каждые 3 месяца Бесплатности тут мало, а заморочек много. Сертификаты Lets encrypt я тоже использую, но для внешних сервисов, например - для этого сайта. А для внутренней домашней сети, мне достаточно самоподписных, с коротким и красивым доменом.\nВывод: использовать сертификаты легко и полезно. # ","date":"27 февраля 2023","externalUrl":null,"permalink":"/posts/2023-02-27-selfsigned-cert/","section":"Статьи","summary":"","title":"Самоподписной ssl сертификат для внутреннего использования","type":"posts"},{"content":"","date":"13 февраля 2023","externalUrl":null,"permalink":"/tags/internet/","section":"Tags","summary":"","title":"Internet","type":"tags"},{"content":" Утечка трафика # На одном из серверов случайным образом заметил огромный трафик: за две недели он составил почти 1,5 ТБ, что для сервера с парой телеграм ботов и несколькими микросервисами - перебор.\nНачинаем искать вредителя. # Сначала проверяю статус файервола (что он активен) и правила. Здесь все в порядке.\nПотом смотрю текущие соединения\nss -tulpan Подозрительных сессий или открытых портов нет.\nЗапускаю мониторинг в реальном времени\niftop Снова никаких подозрительных действий или потребления трафика, все в рамках сотен Kbps, что никак не может привести к сотням гигабайт. Хорошо, оставляю мониторинг на пару часов и через какое-то время что-то \u0026ldquo;сжирает\u0026rdquo; 80ГБ. Однако iftop только показывает сам факт потребления.\nЗапускаю другую утилиту, она отсортирует узлы по отправленному трафику в мегабайтах.\nnethogs -v 3 -s И снова жду пару часов, как рыбак )\nЧерез пару часов \u0026ldquo;ловлю рыбку\u0026rdquo; в виде ip-адреса 185.56.137.2 с портом 8080.\nСмотрю в браузере - бинго! Ookla \u0026ldquo;чтоб его\u0026rdquo; Server! # Один из микросервисов на этом сервере - как раз мониторинг скорости внешнего соединения, который каждый час запускает speedtest и строит красивые графики. И вот этот speedtest \u0026ldquo;пожирал\u0026rdquo; десятки гигабайт за тест.\nОтключаем speedtest - и вуаля Объем трафика снизился на порядки.\nВывод: открыл для себя пару полезных утилит и, да, следить за трафиком - полезно. # ","date":"13 февраля 2023","externalUrl":null,"permalink":"/posts/2023-02-13-traffic-leak/","section":"Статьи","summary":"","title":"Ищем пожирателя трафика","type":"posts"},{"content":"","date":"12 января 2023","externalUrl":null,"permalink":"/tags/vpn/","section":"Tags","summary":"","title":"Vpn","type":"tags"},{"content":"","date":"12 января 2023","externalUrl":null,"permalink":"/tags/wireguard/","section":"Tags","summary":"","title":"Wireguard","type":"tags"},{"content":" Краткое руководство по настройке wireguard vpn # Что такое wireguard и зачем он нужен - статей много, здесь ничего этого не будет. Только шаги и команды.\nClient ---\u0026gt; VPN server ---\u0026gt; Internet\nСервер # Выбираем физический или виртуальный сервер и подключаемся к консоли\nОбновляемся\nsudo apt update \u0026amp;\u0026amp; sudo apt upgrade -y Устанавливаем сам wireguard\nsudo apt install wireguard -y Активируем пересылку трафика, для этого редактируем файл\nsudo nano /etc/sysctl.conf Находим в файле параметр net.ipv4.ip_forward, раскомментируем его и устнавливааем значение =1\nПроверяем\ncat /proc/sys/net/ipv4/ip_forward Должна вернутся 1\nПерезагружаемся\nsudo reboot Подключаемся после перезагрузки\nСгенерируем пару ключей для сервера\nwg genkey | tee server-pri.key | wg pubkey \u0026gt; server-pub.key И для клиента\nwg genkey | tee client-pri.key | wg pubkey \u0026gt; client-pub.key В резултате получим черыре файла:\nserver-pri.key - приватный ключ сервера server-pub.key - публичный ключ сервера client-pri.key - приватный ключ клиента client-pub.key - публичный ключ клиента Приватные ключи должны хранится в надежном месте, публичные - собственно можно публиковать в открытомм доступе. Создаем конфигурацию сервера\nsudo nano /etc/wireguard/wg0.conf Вставляем в него следующее содержимое\n[Interface] # Server Address = 10.10.10.1/24 #адрес нашего виртуального интерфейса ListenPort = 51820 # порт, на котором будет работать wireguard PrivateKey = # сюда вставляем содержимое файла server-pri.key, сгенерированного на предыдущем шаге PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ens1 -j MASQUERADE PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ens1 -j MASQUERADE #здесь указан физический интерфейс сервера ens1, нобходимо скорректировать под свой сверер [Peer] # Client PublicKey = # сюда вставляем содержимое файла client-pub.key, сгенерированного на предыдущем шаге AllowedIPs = 10.10.10.2/32 Запускаем сервис wireguard\nsudo systemctl start wg-quick@wg0 Если сервис wg-quick@wg0 вдруг не запускается - первое, что стоит проверить - присутствует ли в системе пакет resolvconf (если нет - установить sudo apt install resolvconf) И устанавливаем автозапуск\nsudo systemctl enable wg-quick@wg0 Клиент # Создаем конфигурацию клиента\nnano client.conf Вставляем содержимое\n[Interface] PrivateKey = # сюда вставляем содержимое файла client-pri.key Address = 10.10.10.2/32 DNS = 1.1.1.1 [Peer] PublicKey = # сюда вставляем содержимое файла server-pub.key AllowedIPs = 0.0.0.0/0, ::/0 Endpoint = xx.xx.xx.xx:51820 # xx.xx.xx.xx внешний адрес нашего сервера Можем либо файлом перенести эту конфигурацию на клиента, либо через QR-код с помощью qrencode\nsudo apt install qrencode -y Генерируем QR-код либо в отдельный файл\ncat client.conf | qrencode -o client.png Либо отображаем QR-код сразу в консоли\nqrencode -t ansiutf8 \u0026lt; client.conf Активируем клиента и наслаждаемся.\n90% ошибок при настройке - невнимательность. Если что-то не работает - проверяем первым делом ключи и IP-адреса.\n","date":"12 января 2023","externalUrl":null,"permalink":"/posts/2023-01-12-wireguard/","section":"Статьи","summary":"","title":"Настройка wireguard сервера","type":"posts"},{"content":"","date":"12 января 2023","externalUrl":null,"permalink":"/tags/%D1%80%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE/","section":"Tags","summary":"","title":"Руководство","type":"tags"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/notes/","section":"Заметки","summary":"","title":"Заметки","type":"notes"}]