PowerShell vs CMD: пора переезжать?
Все еще пишете батники? PowerShell — это не просто замена CMD, а полноценная среда автоматизации с доступом ко всей мощи .NET.
CMD (ограниченно):
PowerShell:
PowerShell работает с объектами .NET, а не с текстом как CMD. Это дает невероятную гибкость: можно фильтровать, сортировать, группировать данные на лету.
А вы уже перешли на PowerShell? Что стало главным открытием?
🌐 @helcode
Все еще пишете батники? PowerShell — это не просто замена CMD, а полноценная среда автоматизации с доступом ко всей мощи .NET.
CMD (ограниченно):
dir | find "txt"
PowerShell:
# Найти файлы и отсортировать по размеру
Get-ChildItem -Filter "*.txt" | Sort-Object Length -Descending
# Работа с объектами, а не текстом
Get-Process | Where-Object { $_.CPU -gt 100 }
PowerShell работает с объектами .NET, а не с текстом как CMD. Это дает невероятную гибкость: можно фильтровать, сортировать, группировать данные на лету.
А вы уже перешли на PowerShell? Что стало главным открытием?
Переход с CMD на PowerShell — как пересаживаться с велосипеда на спортивный автомобиль. Оба едут, но с разной скоростью и комфортом.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
Автоматизация рутины: планировщик задач на стероидах
Ежедневно делаете одни и те же действия: бэкапы, очистка временных файлов, проверка обновлений? Пора доверить это Планировщику задач.
Создание задачи через PowerShell:
Ежедневный бэкап (backup.ps1):
Планировщик задач Windows — это cron для бедных? Нет, это мощный инструмент, который может запускать задачи по событиям, расписанию, при простое системы.
Какую рутину вы автоматизировали через Планировщик? Поделитесь самыми полезными сценариями!
🌐 @helcode
Ежедневно делаете одни и те же действия: бэкапы, очистка временных файлов, проверка обновлений? Пора доверить это Планировщику задач.
Создание задачи через PowerShell:
$Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File C:\scripts\backup.ps1"
$Trigger = New-ScheduledTaskTrigger -Daily -At "02:00"
Register-ScheduledTask -TaskName "NightlyBackup" -Action $Action -Trigger $Trigger -Description "Automated nightly backup"
Ежедневный бэкап (backup.ps1):
Copy-Item "C:\ImportantData" "D:\Backups\Data_$(Get-Date -Format 'yyyyMMdd')" -Recurse
Планировщик задач Windows — это cron для бедных? Нет, это мощный инструмент, который может запускать задачи по событиям, расписанию, при простое системы.
Какую рутину вы автоматизировали через Планировщик? Поделитесь самыми полезными сценариями!
Планировщик задач — это личный ассистент, который работает 24/7 без отпуска и больничных. Главное — правильно поставить ему задачи.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3❤1😁1🥴1
Windows Subsystem for Linux: лучшее из двух миров
Нужны утилиты Linux, но переустанавливать ОС не хочется? WSL2 позволяет запускать настоящий Linux внутри Windows без виртуализации.
Установка WSL:
Пример автоматизации:
WSL2 — это не эмулятор, а полноценное ядро Linux, работающее параллельно с Windows. Можно запускать любые Linux-утилиты и даже Docker.
А вы используете WSL? Для каких задач он незаменим?
🌐 @helcode
Нужны утилиты Linux, но переустанавливать ОС не хочется? WSL2 позволяет запускать настоящий Linux внутри Windows без виртуализации.
Установка WSL:
# Одна команда для установки
wsl --install
# Или конкретный дистрибутив
wsl --install -d Ubuntu
Пример автоматизации:
# В WSL: мониторинг файлов Windows
inotifywait -m /mnt/c/Users/MyUser/Downloads | while read path action file; do
echo "New file: $file"
# Автоматическая обработка загруженных файлов
done
WSL2 — это не эмулятор, а полноценное ядро Linux, работающее параллельно с Windows. Можно запускать любые Linux-утилиты и даже Docker.
А вы используете WSL? Для каких задач он незаменим?
WSL — это как иметь секретную комнату с Linux, куда можно сбежать из Windows, когда нужен grep или ssh.
Please open Telegram to view this post
VIEW IN TELEGRAM
🤣4❤3
Автогенерация отчетов: PowerShell + Excel
Каждую неделю тратите часы на сбор одинаковых отчетов? PowerShell может автоматически собирать данные и выгружать в красивый Excel-файл.
Пример:
Через COM-объекты PowerShell может управлять любыми приложениями Windows: Excel, Word, Outlook. Это открывает безграничные возможности для автоматизации отчетности.
А вы автоматизируете создание отчетов? Какие данные чаще всего нужно собирать?
🌐 @helcode
Каждую неделю тратите часы на сбор одинаковых отчетов? PowerShell может автоматически собирать данные и выгружать в красивый Excel-файл.
Пример:
# Создание Excel через COM объект
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true
$workbook = $excel.Workbooks.Add()
$worksheet = $workbook.Worksheets.Item(1)
# Заполнение данными о системе
$worksheet.Cells.Item(1,1) = "Computer Name"
$worksheet.Cells.Item(1,2) = $env:COMPUTERNAME
$worksheet.Cells.Item(2,1) = "Disk Space"
$disks = Get-WmiObject Win32_LogicalDisk
$row = 2
foreach ($disk in $disks) {
$worksheet.Cells.Item($row,1) = $disk.DeviceID
$worksheet.Cells.Item($row,2) = "{0:N2} GB" -f ($disk.FreeSpace/1GB)
$row++
}
# Сохранение
$workbook.SaveAs("C:\Reports\SystemReport_$(Get-Date -Format 'yyyyMMdd').xlsx")
$excel.Quit()
Через COM-объекты PowerShell может управлять любыми приложениями Windows: Excel, Word, Outlook. Это открывает безграничные возможности для автоматизации отчетности.
А вы автоматизируете создание отчетов? Какие данные чаще всего нужно собирать?
PowerShell + Excel — это как нанять стажера, который идеально оформляет таблицы и никогда не ошибается. Только без зарплаты и с гарантией результата.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Мониторинг без бюджета: своя система на PowerShell
Нет денег на дорогие системы мониторинга? Можно создать свою за несколько часов на PowerShell с уведомлениями в Telegram.
С помощью PowerShell можно мониторить: место на дисках, загрузку CPU, память, сервисы, логи событий. А через REST API отправлять уведомления куда угодно.
Что бы вы добавили в самодельную систему мониторинга? Делитесь идеями!
🌐 @helcode
Нет денег на дорогие системы мониторинга? Можно создать свою за несколько часов на PowerShell с уведомлениями в Telegram.
# Мониторинг свободного места на диске
function Check-DiskSpace {
$disks = Get-WmiObject Win32_LogicalDisk -Filter "DriveType=3"
foreach ($disk in $disks) {
$freePercent = ($disk.FreeSpace / $disk.Size) * 100
if ($freePercent -lt 10) {
Send-TelegramAlert "⚠️ LOW DISK SPACE: $($disk.DeviceID) - $freePercent% free"
}
}
}
# Отправка в Telegram
function Send-TelegramAlert {
param($Message)
$token = "YOUR_BOT_TOKEN"
$chatId = "YOUR_CHAT_ID"
$url = "https://api.telegram.org/bot$token/sendMessage"
Invoke-RestMethod -Uri $url -Method Post -Body @{
chat_id = $chatId
text = $Message
} | Out-Null
}
# Бесконечный цикл проверки
while ($true) {
Check-DiskSpace
Start-Sleep -Seconds 300 # Проверка каждые 5 минут
}
С помощью PowerShell можно мониторить: место на дисках, загрузку CPU, память, сервисы, логи событий. А через REST API отправлять уведомления куда угодно.
Что бы вы добавили в самодельную систему мониторинга? Делитесь идеями!
Самодельный мониторинг на PowerShell — это как собрать сигнализацию из детского конструктора. Работает неидеально, но зато бесплатно и точно знаешь, как починить.
Please open Telegram to view this post
VIEW IN TELEGRAM
1❤2🔥2👎1🤔1
Война с плавающим багом в CI
Ваш CI-пайплайн падает раз в два дня из-за плавающего теста, и все делают вид, что не замечают? Сегодня объявляем ему войну. Не игнорируем, а автоматизируем поимку.
Это классика: "У меня работает", "Heisenbug*", который исчезает при попытке отладки. Команда привыкает к красному статусу и перестает ему доверять, что убивает саму идею CI. Ручной перезапуск — это не решение, это признание поражения.
➤ Вариант 1 (Bash + Cron): Пишем скрипт, который дергает API вашего CI (например, Jenkins/GitLab), проверяет последний запуск падающего джоба и в случае ошибки автоматически перезапускает его, отправляя уведомление в Slack.
➤ Вариант 2 (GitHub Actions): используем
Речь об устойчивости и проактивном мониторинге. Система не должна полагаться на бдительность человека. Мы встраиваем механизм самовосстановления для известных, но трудноустранимых проблем. Это тот же принцип, что и в
А в вашей команде есть "призрачный" баг? Как вы с ним боретесь: пишете сложные логи, ставите ретраи или просто молитесь?
🌐 @helcode
Ваш CI-пайплайн падает раз в два дня из-за плавающего теста, и все делают вид, что не замечают? Сегодня объявляем ему войну. Не игнорируем, а автоматизируем поимку.
Это классика: "У меня работает", "Heisenbug*", который исчезает при попытке отладки. Команда привыкает к красному статусу и перестает ему доверять, что убивает саму идею CI. Ручной перезапуск — это не решение, это признание поражения.
➤ Вариант 1 (Bash + Cron): Пишем скрипт, который дергает API вашего CI (например, Jenkins/GitLab), проверяет последний запуск падающего джоба и в случае ошибки автоматически перезапускает его, отправляя уведомление в Slack.
# Пример для GitLab CI с использованием jq и curl
PIPELINE_STATUS=$(curl -s --header "PRIVATE-TOKEN: <your_token>" "https://gitlab.com/api/v4/projects/<project_id>/pipelines/latest" | jq -r '.status')
if [ "$PIPELINE_STATUS" = "failed" ]; then
curl --request POST --header "PRIVATE-TOKEN: <your_token>" "https://gitlab.com/api/v4/projects/<project_id>/pipelines/latest/retry"
curl -X POST -H 'Content-type: application/json' --data '{"text":"Плавающий тест снова упал. Автоперезапуск...»"}' $SLACK_WEBHOOK
fi
➤ Вариант 2 (GitHub Actions): используем
schedule и джоб с условием для автоматического перезапуска только конкретного флаки-теста.jobs:
retry_flaky_test:
if: failure() && contains(github.event.head_commit.message, 'retry flaky test')
runs-on: ubuntu-latest
steps:
- name: Retry the flaky test suite
run: |
echo "Автоматический ретрай запущен"
Речь об устойчивости и проактивном мониторинге. Система не должна полагаться на бдительность человека. Мы встраиваем механизм самовосстановления для известных, но трудноустранимых проблем. Это тот же принцип, что и в
livenessProbe в Kubernetes: если система нездорова, ее нужно перезапустить, прежде чем бить в колокола.А в вашей команде есть "призрачный" баг? Как вы с ним боретесь: пишете сложные логи, ставите ретраи или просто молитесь?
P.S. Автоматический ретрай — это не починка бага, это костыль. Но лучше автоматический костыль, чем кривые костыли ручных ретраев.
*Гейзенбаг - жаргонный термин, используемый в программировании для описания программной ошибки, которая исчезает или меняет свои свойства при попытке её обнаружения.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1👍1
Один скрипт для владения всем. Поднимаем окружение за 60 секунд.
Тратите час на подготовку тестового окружения для нового разработчика? Или для себя на чистой машине? Пора это автоматизировать одним скриптом. "It works on my machine" должно умереть.
➤ Вариант 1 (Vagrant + Shell): классика жанра.
➤ Вариант 2 (Docker Compose): идеально для микросервисных и изолированных окружений. Один файл
➤ Вариант 3 (Ansible Playbook): удобно для физических серверов и тонкой настройки. Ansible тащит за собой философию идемпотентности.
Это декларативный подход в миниатюре. Вы описываете *желаемое состояние* окружения (софт, версии, конфиги), а инструмент приводит систему к этому состоянию. Больше никаких "мануалов". Этот же принцип масштабируется до Terraform для облаков и Kubernetes для оркестрации.
А вы как поднимаете дев-окружение? Старый добрый Vagrant, модный DevContainer, или может вообще Nix? Поделитесь своими кейсами.
🌐 @helcode
Тратите час на подготовку тестового окружения для нового разработчика? Или для себя на чистой машине? Пора это автоматизировать одним скриптом. "It works on my machine" должно умереть.
README.md с пунктами "установи это, сконфигурируй то, выполни вон ту команду" — это источник ошибок и потеря времени. Расхождение в версиях, пропущенные переменные окружения, забытые миграции БД.➤ Вариант 1 (Vagrant + Shell): классика жанра.
Vagrantfile описывает виртуальную машину, а вложенный shell-скрипт делает всю грязную работу.➤ Вариант 2 (Docker Compose): идеально для микросервисных и изолированных окружений. Один файл
docker-compose.yml поднимает БД, кеш, брокер сообщений и ваше приложение.version: '3.8'
services:
db:
image: postgres:14
environment:
POSTGRES_DB: myapp
cache:
image: redis:alpine
app:
build: .
depends_on:
- db
- cache
➤ Вариант 3 (Ansible Playbook): удобно для физических серверов и тонкой настройки. Ansible тащит за собой философию идемпотентности.
- name: Set up development environment
hosts: localhost
tasks:
- name: Ensure Node.js is installed
package:
name: nodejs
state: present
Это декларативный подход в миниатюре. Вы описываете *желаемое состояние* окружения (софт, версии, конфиги), а инструмент приводит систему к этому состоянию. Больше никаких "мануалов". Этот же принцип масштабируется до Terraform для облаков и Kubernetes для оркестрации.
А вы как поднимаете дев-окружение? Старый добрый Vagrant, модный DevContainer, или может вообще Nix? Поделитесь своими кейсами.
P.S. Потратьте 4 часа на написание скрипта, который сэкономит 100 часов вашей команды. Математика простая.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
Самолечение для Kubernetes: Beyond `kubectl get pods`
В сотый раз получаем алерт, что под упал. В сотый раз вручную делаем
➤ Вариант 1 (Нативные механизмы K8s):
➤ Вариант 2 (Operator Pattern): для сложных stateful приложений (БД, Kafka). Кастомный контроллер (Operator) следит за состоянием и может делать сложные вещи: делать бэкап, ребалансировать партиции*, увеличивать объем хранилища.
➤ Вариант 3 (Скрипт на Bash/Python + CronJob): если нативных механизмов мало. Например, скрипт, который проверяет логи на наличие конкретной ошибки и выполняет кастомную операцию по ее исправлению.
Это воплощение принципа "выбирай исправление, а не оповещение". Мы движемся от мониторинга ("что-то сломалось") к автоматическому реагированию ("я уже пытаюсь это починить"). Это краеугольный камень самоцелительных (self-healing) систем.
А ваши поды умеют лечить себя сами? Используете только `livenessProbe` или пишете кастомные операторы? Давайте обсудим, какие сценарии самолечения самые крутые.
🌐 @helcode
kubectl get pods | grep CrashLoopBackOff - это не диагноз, это крик о помощи. Давайте научим кубер самолечению, чтобы он не просто сообщал о проблеме, а пытался ее исправить.В сотый раз получаем алерт, что под упал. В сотый раз вручную делаем
kubectl delete pod .... Это цифровая "яжемать :)", а не работа инженера. Система должна заботиться о себе сама.➤ Вариант 1 (Нативные механизмы K8s):
livenessProbe и readinessProbe — это основа. Если проба живучести падает, kubelet убивает и пересоздает контейнер.livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
➤ Вариант 2 (Operator Pattern): для сложных stateful приложений (БД, Kafka). Кастомный контроллер (Operator) следит за состоянием и может делать сложные вещи: делать бэкап, ребалансировать партиции*, увеличивать объем хранилища.
➤ Вариант 3 (Скрипт на Bash/Python + CronJob): если нативных механизмов мало. Например, скрипт, который проверяет логи на наличие конкретной ошибки и выполняет кастомную операцию по ее исправлению.
# Пример: поиск и лечение утечки памяти
if kubectl logs my-pod --since=10m | grep -q "OutOfMemoryError"; then
kubectl rollout restart deployment my-app
fi
Это воплощение принципа "выбирай исправление, а не оповещение". Мы движемся от мониторинга ("что-то сломалось") к автоматическому реагированию ("я уже пытаюсь это починить"). Это краеугольный камень самоцелительных (self-healing) систем.
А ваши поды умеют лечить себя сами? Используете только `livenessProbe` или пишете кастомные операторы? Давайте обсудим, какие сценарии самолечения самые крутые.
P.P.S. Идеал — это когда вы спокойно спите, а ваш кластер ночью сам перезапускает поды, рестартит деплоименты и даже делает кофе. Ну, почти.
*Партиции (или секции) — это отдельные физические или логические части, на которые разбиваются большие наборы данных, например, таблицы в базах данных или разделы в системах обработки данных.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1🔥1
Идемпотентность: cкрипт не должно быть больно запустить дважды.
Боитесь запустить скрипт развертывания во второй раз? Он создаст дубликаты файлов, перезапишет конфиги и сломает всё? Поздравляю, ваш скрипт не идемпотентен. Сегодня учимся писать скрипты, которые безопасно запускать сколько угодно раз.
"Скрипт сработал, но я не уверен... Запустить еще раз на всякий случай? Ой, а теперь всё сломалось". Знакомо? Это следствие императивного подхода без проверок.
➤ Вариант 1 (Bash): проверяем существование файла, директории, запущенного процесса.
➤ Вариант 2 (Ansible): вся философия Ansible построена на идемпотентности. Каждая таска сама проверяет, нужно ли что-то менять.
➤ Вариант 3 (Terraform):
Идемпотентность — это свойство операции, при котором ее многократное выполнение дает тот же результат, что и однократное. Как перезагрузка страницы. Это основа надежности. Это позволяет безопасно ретраить операции, применять конфиги и не бояться "лишних" запусков.
А ваши скрипты идемпотентны? Как вы этого добиваетесь? if-проверки, флаги --force, или доверяете только декларативным инструментам?
🌐 @helcode
Боитесь запустить скрипт развертывания во второй раз? Он создаст дубликаты файлов, перезапишет конфиги и сломает всё? Поздравляю, ваш скрипт не идемпотентен. Сегодня учимся писать скрипты, которые безопасно запускать сколько угодно раз.
"Скрипт сработал, но я не уверен... Запустить еще раз на всякий случай? Ой, а теперь всё сломалось". Знакомо? Это следствие императивного подхода без проверок.
➤ Вариант 1 (Bash): проверяем существование файла, директории, запущенного процесса.
# Вместо простого touch
if [ ! -f "/etc/config.yaml" ]; then
touch /etc/config.yaml
cp default_config.yaml /etc/config.yaml
fi
➤ Вариант 2 (Ansible): вся философия Ansible построена на идемпотентности. Каждая таска сама проверяет, нужно ли что-то менять.
- name: Ensure user "deploy" exists
user:
name: deploy
state: present
groups: sudo
# Запускайте хоть 1000 раз — пользователь будет создан только один раз.
➤ Вариант 3 (Terraform):
terraform apply вычисляет разницу между желаемым и текущим состоянием и применяет только необходимые изменения. Это апофеоз декларативной идемпотентности.Идемпотентность — это свойство операции, при котором ее многократное выполнение дает тот же результат, что и однократное. Как перезагрузка страницы. Это основа надежности. Это позволяет безопасно ретраить операции, применять конфиги и не бояться "лишних" запусков.
А ваши скрипты идемпотентны? Как вы этого добиваетесь? if-проверки, флаги --force, или доверяете только декларативным инструментам?
P.S. Идемпотентный скрипт — это как вежливый человек: сколько раз ни спроси, ответит одинаково и не разозлится.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5💯1
Скрипт-помощник для Git. Автоматизируем рутину.
Ваш ежедневный ритуал:
Однообразные последовательности Git-команд — это идеальный кандидат для автоматизации. Они отнимают время, и в них легко ошибиться, сделав
➤ Вариант 1 (Git Aliases): просто и эффективно прямо в
➤ Вариант 2 (Bash/Zsh Functions): для более сложной логики.
➤ Вариант 3 (Node.js / Python-скрипт): для комплексных операций, например, создания Pull Request. Используем GitHub CLI (
Это автоматизация рабочих процессов (Workflow Automation). Вы идентифицируете повторяющийся паттерн действий (часто кросс-платформенный: терминал, браузер, IDE) и упаковываете его в единую команду. Это снижает когнитивную нагрузку и предотвращает ошибки.
А какие у вас есть самые полезные гитовые алиасы или скрипты? Поделитесь своими "магическими заклинаниями" в комментах!
🌐 @helcode
Ваш ежедневный ритуал:
git add ., git commit -m "fix", git push, git checkout dev, git pull, git merge feature/...... Пора делегировать это роботу.Однообразные последовательности Git-команд — это идеальный кандидат для автоматизации. Они отнимают время, и в них легко ошибиться, сделав
git push --force не в ту ветку.➤ Вариант 1 (Git Aliases): просто и эффективно прямо в
.gitconfig.[alias]
co = checkout
cm = commit -m
br = branch
lol = log --oneline --graph --all
publish = !git push -u origin $(git branch --show-current)
➤ Вариант 2 (Bash/Zsh Functions): для более сложной логики.
# Поместить в ~/.bashrc или ~/.zshrc
gup() {
git add . && git commit -m "$1" && git push
}
# Использование: `gup "мой коммит"`
➤ Вариант 3 (Node.js / Python-скрипт): для комплексных операций, например, создания Pull Request. Используем GitHub CLI (
gh).// Использование: node pr.js "My feature"
const { execSync } = require('child_process');
const title = process.argv[2] || 'Default title';
execSync(`gh pr create --title "${title}" --body "" --base dev`, { stdio: 'inherit' });
Это автоматизация рабочих процессов (Workflow Automation). Вы идентифицируете повторяющийся паттерн действий (часто кросс-платформенный: терминал, браузер, IDE) и упаковываете его в единую команду. Это снижает когнитивную нагрузку и предотвращает ошибки.
А какие у вас есть самые полезные гитовые алиасы или скрипты? Поделитесь своими "магическими заклинаниями" в комментах!
Please open Telegram to view this post
VIEW IN TELEGRAM
Мониторинг, который пишет сам себя. Парсим логи с jq.
Вам нужна сводка по ошибкам из логов, но влезать в Kibana/Splunk лень? Научимся за 5 минут и 3 команды в терминале получать готовые отчеты.
Логи есть, но информация похоронена под горой JSON-объектов. Ручной поиск и агрегация занимают время. Нужен быстрый способ отвечать на вопросы: "Какая самая частая ошибка за последний час?", "Сколько пользователей получили 500-ю ошибку?".
➤ Вариант 1 (Классика Bash):
➤ Вариант 2 (удобство
➤ Вариант 3 (Python-скрипт): для максимальной гибкости.
Речь о событийной агрегации. Мы превращаем поток сырых событий (логов) в структурированные данные, пригодные для анализа.
А как вы быстро анализируете логи без тяжеловесных систем? Дружите с jq, или предпочитаете awk/grep? Покажите свои самые крутые команды для парсинга!
🌐 @helcode
Вам нужна сводка по ошибкам из логов, но влезать в Kibana/Splunk лень? Научимся за 5 минут и 3 команды в терминале получать готовые отчеты.
Логи есть, но информация похоронена под горой JSON-объектов. Ручной поиск и агрегация занимают время. Нужен быстрый способ отвечать на вопросы: "Какая самая частая ошибка за последний час?", "Сколько пользователей получили 500-ю ошибку?".
➤ Вариант 1 (Классика Bash):
grep, awk, sort, uniq.# Топ-5 самых частых ошибок
grep "ERROR" app.log | awk '{print $4}' | sort | uniq -c | sort -nr | head -5
➤ Вариант 2 (удобство
jq): если логи в JSON — вы обязаны знать jq.# Считаем 500-е ошибки по уникальным пользователям
cat nginx.log | jq -r 'select(.status == 500) | .user_id' | sort | uniq | wc -l
# Группируем ошибки по типу и считаем
cat app.log | jq -s 'group_by(.error_type) | map({type: .[0].error_type, count: length}) | sort_by(-.count)'
➤ Вариант 3 (Python-скрипт): для максимальной гибкости.
import json
from collections import Counter
errors = []
with open('app.log') as f:
for line in f:
log = json.loads(line)
if log['level'] == 'ERROR':
errors.append(log['error_type'])
print(Counter(errors).most_common(5))
Речь о событийной агрегации. Мы превращаем поток сырых событий (логов) в структурированные данные, пригодные для анализа.
jq — это декларативный язык запросов для JSON, такой же мощный, как SQL для баз данных. Вы описываете, *что* хотите получить, а не *как* это искать.А как вы быстро анализируете логи без тяжеловесных систем? Дружите с jq, или предпочитаете awk/grep? Покажите свои самые крутые команды для парсинга!
P.S. Умение быстро вытащить нужную цифру из кучи JSON — это суперспособность, которая делает вас героем митапа.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
"У меня работает" — убиваем фразу навсегда. Контейнеризация.
"У меня работает" — это не чудо, это недостаток контроля над окружением. Сегодня хороним эту отмазку, упаковывая приложение в Docker-контейнер.
Разработчик работает на Mac с Node.js 18, продакшен на Ubuntu с Node.js 16. Результат — непредсказуемое поведение. Разные версии библиотек, разные системные настройки. Хаос.
➤ Вариант 1 (Простой Dockerfile): базовый сценарий для Node.js-приложения.
➤ Вариант 2 (Мультистейдж-билд): для уменьшения размера образа и повышения безопасности.
➤ Вариант 3 (Docker Compose для полноценного стека): сразу описываем приложение и все его зависимости (БД, кеш).
Это изоляция зависимостей и воспроизводимость. Контейнер — это единица стандартизации. Вы инкапсулируете код, его зависимости, среду выполнения и системные библиотеки в один неизменяемый артефакт. Этот же артефакт проходит весь путь от разработки до продакшена. Больше никаких сюрпризов.
А вы уже похоронили "у меня работает"? Используете "толстые" образы (ubuntu) или "тощие" (alpine, distroless)? Спорите на тему Dockerfile best practices?
🌐 @helcode
"У меня работает" — это не чудо, это недостаток контроля над окружением. Сегодня хороним эту отмазку, упаковывая приложение в Docker-контейнер.
Разработчик работает на Mac с Node.js 18, продакшен на Ubuntu с Node.js 16. Результат — непредсказуемое поведение. Разные версии библиотек, разные системные настройки. Хаос.
➤ Вариант 1 (Простой Dockerfile): базовый сценарий для Node.js-приложения.
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "server.js"]
➤ Вариант 2 (Мультистейдж-билд): для уменьшения размера образа и повышения безопасности.
# Стадия сборки
FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm run build
# Стадия продакшена
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
➤ Вариант 3 (Docker Compose для полноценного стека): сразу описываем приложение и все его зависимости (БД, кеш).
Это изоляция зависимостей и воспроизводимость. Контейнер — это единица стандартизации. Вы инкапсулируете код, его зависимости, среду выполнения и системные библиотеки в один неизменяемый артефакт. Этот же артефакт проходит весь путь от разработки до продакшена. Больше никаких сюрпризов.
А вы уже похоронили "у меня работает"? Используете "толстые" образы (ubuntu) или "тощие" (alpine, distroless)? Спорите на тему Dockerfile best practices?
P.S. После этого единственное, что "работает у вас", — это сам Docker. И это гораздо проще починить.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
Автоматический деплой по тэгу. Release и забыл.
Вручную деплоить новую версию в прод — это как вручную переключать стрелки на железной дороге. Пора настроить автоматический поезд, который едет сам при появлении нового тэга
Процесс релиза — это стресс. Нужно не забыть собрать билд, залить его на сервер, запустить миграции, обновить конфиги. Человеческий фактор на каждом шагу.
➤ Вариант 1 (GitHub Actions): триггер на
➤ Вариант 2 (GitLab CI): аналогично, используем правила.
➤ Вариант 3 (Скрипт на Bash): локальный хук, который при пуше тэга запускает деплой.
Это событийно-ориентированная архитектура* в CI/CD. Событие "появился новый тэг" автоматически запускает цепочку действий (пайплайн). Вам не нужно "нажимать на кнопку", система реагирует на изменения состояния сама. Это уменьшает время между готовностью кода и его доставкой.
А как у вас настроен деплой? Ручной кнопочный, автоматический по тэгу, или хитрый канарейка-деплой?
🌐 @helcode
Вручную деплоить новую версию в прод — это как вручную переключать стрелки на железной дороге. Пора настроить автоматический поезд, который едет сам при появлении нового тэга
v1.*.Процесс релиза — это стресс. Нужно не забыть собрать билд, залить его на сервер, запустить миграции, обновить конфиги. Человеческий фактор на каждом шагу.
➤ Вариант 1 (GitHub Actions): триггер на
push с тэгом.on:
push:
tags:
- 'v*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: |
./deploy-prod.sh
➤ Вариант 2 (GitLab CI): аналогично, используем правила.
deploy_prod:
script:
- scp ./app.tar.gz user@prod-server:/app/
- ssh user@prod-server "tar -xzf /app/app.tar.gz -C /app/"
only:
- tags
except:
- branches
➤ Вариант 3 (Скрипт на Bash): локальный хук, который при пуше тэга запускает деплой.
# В .git/hooks/post-push (примерно)
if [[ $1 == *"refs/tags/v"* ]]; then
echo "Запуск деплоя для тэга ${1##*/}..."
ssh deploy@server "cd /app && ./update.sh ${1##*/}"
fi
Это событийно-ориентированная архитектура* в CI/CD. Событие "появился новый тэг" автоматически запускает цепочку действий (пайплайн). Вам не нужно "нажимать на кнопку", система реагирует на изменения состояния сама. Это уменьшает время между готовностью кода и его доставкой.
А как у вас настроен деплой? Ручной кнопочный, автоматический по тэгу, или хитрый канарейка-деплой?
P.S. Идеальный релиз — это когда вы закоммитили тэг, пошли пить кофе, а система все сделала за вас. И не сломала.
*Архитектура, управляемая событиями (англ. event-driven architecture, EDA) является шаблоном архитектуры программного обеспечения, позволяющим создание, определение, потребление и реакцию на события (ссылка на Википедию, для интересующихся).
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍2
Скрипт-уборщик: Освобождаем место на диске.
Ваш диск забит образами Docker, логами, старыми билдами.
Вручную чистить
➤ Вариант 1 (Bash-скрипт + Cron):
➤ Вариант 2 (Ansible для множества серверов): плейбук, который запускает "уборку" на всех ваших серверах раз в неделю.
Это проактивное управление ресурсами. Вместо того чтобы реагировать на проблему (закончилось место), мы предотвращаем ее с помощью регулярных, автоматизированных действий. Это классический сценарий для Cron — планировщика задач, который является прадедом всей современной оркестрации.
А что чистите вы? Docker, кеш пакетных менеджеров (npm, pip), старые бэкапы? Давайте составим список самых "мусорных" директорий в IT-мире.
🌐 @helcode
Ваш диск забит образами Docker, логами, старыми билдами.
df -h вызывает панику. Пора написать скрипт-дворника, который будет делать уборку за вас.Вручную чистить
/tmp, docker system prune, удалять старые билды — скучно и легко забыть. Место заканчивается в самый неподходящий момент.➤ Вариант 1 (Bash-скрипт + Cron):
#!/bin/bash
echo "Начинается уборка..."
docker system prune -f
find /var/log -name "*.log" -type f -mtime +7 -delete
find /tmp -type f -atime +1 -delete
echo "Уборка завершена. Свободно: $(df -h / | awk 'NR==2 {print $4}')"
➤ Вариант 2 (Ansible для множества серверов): плейбук, который запускает "уборку" на всех ваших серверах раз в неделю.
- name: Clean up old logs
hosts: all
tasks:
- name: Remove log files older than 30 days
file:
path: "/var/log/{{ item }}"
state: absent
loop:
- "*.log.1"
- "*.gz"
Это проактивное управление ресурсами. Вместо того чтобы реагировать на проблему (закончилось место), мы предотвращаем ее с помощью регулярных, автоматизированных действий. Это классический сценарий для Cron — планировщика задач, который является прадедом всей современной оркестрации.
А что чистите вы? Docker, кеш пакетных менеджеров (npm, pip), старые бэкапы? Давайте составим список самых "мусорных" директорий в IT-мире.
P.S. Этот скрипт сэкономит вам не только гигабайты, но и нервы в пятницу вечером, когда нужно срочно задеплоить хотфикс.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤2
Декларативные конфиги: Ваша инфраструктура — это код.
Ваши серверы — это снежинки, уникальные и хрупкие? Пора превратить их в скотч, который можно клонировать и выбросить без сожалений. С помощью кода.
"А на этом сервере вот такой костыль, он тут с прошлого года", "без этого костыля не поднимается". Ручные правки, забытые конфиги, дрифт — когда реальная инфраструктура расходится с тем, что вы помните.
➤ Вариант 1 (Terraform): описываем облачные ресурсы (VM, сети, БД) как код.
➤ Вариант 2 (Ansible): описываем конфигурацию серверов (софт, файлы, службы).
Декларативный подход. Вы говорите системе *ЧТО* вы хотите получить ("сервер с Nginx"), а не *КАК* это сделать ("залогинься по SSH, выполни
А ваша инфраструктура — это код? Используете Terraform, Pulumi, Ansible? Или все еще рисуете диаграмки вручную в облачной консоли?
🌐 @helcode
Ваши серверы — это снежинки, уникальные и хрупкие? Пора превратить их в скотч, который можно клонировать и выбросить без сожалений. С помощью кода.
"А на этом сервере вот такой костыль, он тут с прошлого года", "без этого костыля не поднимается". Ручные правки, забытые конфиги, дрифт — когда реальная инфраструктура расходится с тем, что вы помните.
➤ Вариант 1 (Terraform): описываем облачные ресурсы (VM, сети, БД) как код.
resource "aws_instance" "web" {
ami = "ami-123456"
instance_type = "t3.micro"
tags = {
Name = "HelloWorld"
}
}➤ Вариант 2 (Ansible): описываем конфигурацию серверов (софт, файлы, службы).
- name: Install and start Nginx
hosts: webservers
tasks:
- name: Install nginx
package: name=nginx state=present
- name: Start nginx
systemd: name=nginx state=started enabled=yes
Декларативный подход. Вы говорите системе *ЧТО* вы хотите получить ("сервер с Nginx"), а не *КАК* это сделать ("залогинься по SSH, выполни
apt update, потом..."). Это дает предсказуемость, версионность (конфиги в Git) и позволяет практиковать "phoenix deployments" — когда серверы не лечатся, а убиваются и создаются заново.А ваша инфраструктура — это код? Используете Terraform, Pulumi, Ansible? Или все еще рисуете диаграмки вручную в облачной консоли?
P.S. Научившись описывать инфраструктуру кодом, вы будете с ужасом вспоминать, как раньше жили. Это точка невозврата.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
Умные уведомления в Slack. Фильтруем шум.
Ваш Slack-канал с алертами похож на спам-бот? 95% сообщений — это информационный шум, который все игнорируют. Пора научить его отправлять только важное, да еще и в структурированном виде.
"Алерты замылили глаз", "Снова падает тест Карла, проигнорируем". Когда алертов слишком много, люди перестают на них реагировать. Пропускают действительно критичные сообщения.
➤ Вариант 1 (Bash-скрипт с логикой): не просто
➤ Вариант 2 (Python-скрипт с форматированием): используем Slack Blocks для красивого сообщения.
Это "сокращение шума и повышение сигнала" (Signal-to-Noise Ratio / SNR) в мониторинге. Автоматизация — это не только про "делать", но и про "не делать" лишнего. Фильтрация, агрегация и приоритизация уведомлений — такая же важная часть инженерной культуры, как и сам мониторинг.
А ваш Slack умный или кричащий? Как вы боретесь с алертным шумом? Пишете сложные фильтры в Prometheus Alertmanager, или правила прямо в скриптах?
🌐 @helcode
Ваш Slack-канал с алертами похож на спам-бот? 95% сообщений — это информационный шум, который все игнорируют. Пора научить его отправлять только важное, да еще и в структурированном виде.
"Алерты замылили глаз", "Снова падает тест Карла, проигнорируем". Когда алертов слишком много, люди перестают на них реагировать. Пропускают действительно критичные сообщения.
➤ Вариант 1 (Bash-скрипт с логикой): не просто
curl, а с условиями.# Отправляем только если ошибка критическая
if [ "$ERROR_SEVERITY" = "CRITICAL" ]; then
curl -X POST ... -d '{"text":"🔥 CRITICAL: ..."}'
fi
➤ Вариант 2 (Python-скрипт с форматированием): используем Slack Blocks для красивого сообщения.
blocks = [
{
"type": "section",
"text": {"type": "mrkdwn", "text": f"*Deployment Failed* \n Service: {service}\n Reason: {error}"}
},
{
"type": "actions",
"elements": [{"type": "button", "text": {"type": "plain_text", "text": "View Logs"}, "url": log_url}]
}
]
requests.post(slack_webhook, json={"blocks": blocks})
Это "сокращение шума и повышение сигнала" (Signal-to-Noise Ratio / SNR) в мониторинге. Автоматизация — это не только про "делать", но и про "не делать" лишнего. Фильтрация, агрегация и приоритизация уведомлений — такая же важная часть инженерной культуры, как и сам мониторинг.
А ваш Slack умный или кричащий? Как вы боретесь с алертным шумом? Пишете сложные фильтры в Prometheus Alertmanager, или правила прямо в скриптах?
P.S. Идеальный алерт в Slack — это когда он приходит, и вся команда одновременно хватается за клавиатуру. А не закатывает глаза.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
SQL-скрипты — тоже автоматизация. Миграции и не только.
Выполняете миграции БД вручную, копируя SQL в консоль? Или обновляете права для нового пользователя через UI? Пора автоматизировать и это.
База данных — часто самое государство в государстве. Ее изменения делаются вручную, не версионируются, и о них легко забыть. Это создает огромный риск при развертывании.
➤ Вариант 1 (Миграционные инструменты): Flyway, Liquibase, Goose. Они применяют SQL-скрипты с версиями и следят за тем, чтобы схема БД всегда была актуальной.
➤ Вариант 2 (Скрипт на Bash/Python): для одноразовых задач, например, создания бэкапа или выгрузки данных.
➤ Вариант 3 (Находим дубли в данных с помощью SQL): автоматизируем поиск проблем.
Это Database as Code и идемпотентность в действии. Миграционные скрипты должны быть написаны так, чтобы их можно было применить многократно без ошибок (использование
А как вы управляете изменениями БД? Ручные скрипты, миграционные фреймворки, или ORM делает все за вас? Самая страшная история про сломанную миграцию?
🌐 @helcode
Выполняете миграции БД вручную, копируя SQL в консоль? Или обновляете права для нового пользователя через UI? Пора автоматизировать и это.
База данных — часто самое государство в государстве. Ее изменения делаются вручную, не версионируются, и о них легко забыть. Это создает огромный риск при развертывании.
➤ Вариант 1 (Миграционные инструменты): Flyway, Liquibase, Goose. Они применяют SQL-скрипты с версиями и следят за тем, чтобы схема БД всегда была актуальной.
-- V1__Create_user_table.sql
CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR(100));
➤ Вариант 2 (Скрипт на Bash/Python): для одноразовых задач, например, создания бэкапа или выгрузки данных.
# Дамп и загрузка в S3
pg_dump mydb > backup.sql
aws s3 cp backup.sql s3://my-bucket/backup-$(date +%Y%m%d).sql
➤ Вариант 3 (Находим дубли в данных с помощью SQL): автоматизируем поиск проблем.
-- Найти пользователей-дубликатов по email
SELECT email, COUNT(*)
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
Это Database as Code и идемпотентность в действии. Миграционные скрипты должны быть написаны так, чтобы их можно было применить многократно без ошибок (использование
CREATE TABLE IF NOT EXISTS или ALTER TABLE с проверками). Это делает изменения в самой важной части системы — данных — предсказуемыми и повторяемыми.А как вы управляете изменениями БД? Ручные скрипты, миграционные фреймворки, или ORM делает все за вас? Самая страшная история про сломанную миграцию?
P.S. Автоматизированная миграция БД — это как парашют: его отсутствие вы замечаете только один раз, в самый неподходящий момент.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
Сборка самописных утилит на Go. Когда Bash уже мал.
Ваш bash-скрипт для деплоя разросся до 500 строк и стал похож на Франкенштейна? Пора переписать его на Go и получить одну бинарку, которую можно кинуть куда угодно.
Bash отлично справляется с маленькими задачами, но когда нужна сложная логика, работа с HTTP API, парсинг сложного JSON — он становится неуклюжим и трудночитаемым.
➤ Простой пример на Go: утилита, которая ходит в API, проверяет статус и шлет уведомление.
➤ Почему Go? Простая компиляция в один бинарный файл, кросс-компиляция, удобная стандартная библиотека (особенно для сетевых задач), нет зависимостей в рантайме.
Речь о выборе правильного инструмента. Bash — для быстрых, тексто-ориентированных задач. Python — для скриптов, где важна скорость разработки и есть много библиотек. Go — для высокопроизводительных, надежных утилит, которые должны работать без проблем в любом окружении.
А вы уже перерастали Bash? В какой момент понимали, что пора браться за "нормальный" язык? И что выбирали: Go, Python, Ruby?
🌐 @helcode
Ваш bash-скрипт для деплоя разросся до 500 строк и стал похож на Франкенштейна? Пора переписать его на Go и получить одну бинарку, которую можно кинуть куда угодно.
Bash отлично справляется с маленькими задачами, но когда нужна сложная логика, работа с HTTP API, парсинг сложного JSON — он становится неуклюжим и трудночитаемым.
➤ Простой пример на Go: утилита, которая ходит в API, проверяет статус и шлет уведомление.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
resp, err := http.Get("https://api.status.io/1.0/status/xyz")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var status Status
json.NewDecoder(resp.Body).Decode(&status)
if status.Status != "operational" {
fmt.Println("Сервис лежит!")
os.Exit(1)
}
fmt.Println("Всё ок!")
}
type Status struct {
Status string `json:"status"`
}
➤ Почему Go? Простая компиляция в один бинарный файл, кросс-компиляция, удобная стандартная библиотека (особенно для сетевых задач), нет зависимостей в рантайме.
Речь о выборе правильного инструмента. Bash — для быстрых, тексто-ориентированных задач. Python — для скриптов, где важна скорость разработки и есть много библиотек. Go — для высокопроизводительных, надежных утилит, которые должны работать без проблем в любом окружении.
А вы уже перерастали Bash? В какой момент понимали, что пора браться за "нормальный" язык? И что выбирали: Go, Python, Ruby?
P.S. Написание утилит на Go — это как собрать своего робота из Лего: удобно, надежно и невероятно приятно, когда он работает.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
Автоматический ревью кода: cтатический анализ и линтеры*
Ревьюить чужой код и спорить о стиле — последнее, чем вы хотите заниматься в пятницу вечером. Поручите это роботу, чтобы сосредоточиться на архитектуре.
Бесконечные споры о табах / пробелах, забытые
Вариант 1 (ESLint / Prettier): для JavaScript/TypeScript.
Вариант 2 (Shell-скрипт для Git Hooks): запускаем линтеры перед коммитом.
Вариант 3 (GitHub Actions): автоматический запуск линтеров в PR.
Смещение леве — находим и исправляем ошибки как можно раньше, еще до человеческого ревью. Это экономит время и нервы всей команды.
А ваш CI уже ругается на кривой код? Какие линтеры используете: ESLint, RuboCop, Black, gofmt?
🌐 @helcode
Ревьюить чужой код и спорить о стиле — последнее, чем вы хотите заниматься в пятницу вечером. Поручите это роботу, чтобы сосредоточиться на архитектуре.
Бесконечные споры о табах / пробелах, забытые
console.log, неиспользуемые переменные. Ревью превращается в обсуждение формальностей, а не логики.Вариант 1 (ESLint / Prettier): для JavaScript/TypeScript.
{
"rules": {
"no-unused-vars": "error",
"prefer-const": "warn"
}
}Вариант 2 (Shell-скрипт для Git Hooks): запускаем линтеры перед коммитом.
# .git/hooks/pre-commit
#!/bin/bash
if ! npm run lint; then
echo "Линтер ругается! Фиксим ошибки перед коммитом."
exit 1
fi
Вариант 3 (GitHub Actions): автоматический запуск линтеров в PR.
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run lint
Смещение леве — находим и исправляем ошибки как можно раньше, еще до человеческого ревью. Это экономит время и нервы всей команды.
А ваш CI уже ругается на кривой код? Какие линтеры используете: ESLint, RuboCop, Black, gofmt?
P.S. Идеальное ревью кода — когда человек проверяет архитектуру, а робот отлавливает опечатки.
*Линтер — это программа-инструмент для статического анализа кода, которая автоматически проверяет его на наличие ошибок, нарушений стилей и других недочетов. Его основная задача — находить потенциальные проблемы ещё до запуска приложения, что помогает разработчикам писать более чистый, понятный и единообразный код. Среди популярных линтеров, например, для Python — Ruff, Pylint и Flake8.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
Мониторим здоровье системы одним скриптом
Нужно быстро проверить состояние сервера, но нет времени залазить в Grafana? Напишем скрипт-диагност, который покажет все проблемы за 5 секунд.
Когда система тормозит, вы в панике запускаете 10 команд подряд:
Вариант 1 (Bash-скрипт):
Вариант 2 (Python-скрипт с цветным выводом):
Агрегация метрик — собираем разрозненные данные в единую панель управления. Это основа любого мониторинга, от простых скриптов до сложных Prometheus + Grafana.
А какие команды вы первым делом запускаете при проблемах? Давайте составим список must-have команд для диагностики.
🌐 @helcode
Нужно быстро проверить состояние сервера, но нет времени залазить в Grafana? Напишем скрипт-диагност, который покажет все проблемы за 5 секунд.
Когда система тормозит, вы в панике запускаете 10 команд подряд:
top, df -h, docker ps, netstat -tulpn...Вариант 1 (Bash-скрипт):
#!/bin/bash
echo "=== HEALTH CHECK ==="
echo "CPU: $(top -bn1 | grep load | awk '{printf "%.2f\n", $(NF-2)}')"
echo "Memory: $(free -m | awk 'NR==2{printf "%.2f%%", $3*100/$2}')"
echo "Disk: $(df -h / | awk 'NR==2{print $5}')"
echo "Docker: $(docker ps --format "table {{.Names}}\t{{.Status}}" | grep -v Up)"
Вариант 2 (Python-скрипт с цветным выводом):
import psutil
import docker
print(f"CPU: {psutil.cpu_percent()}%")
print(f"Memory: {psutil.virtual_memory().percent}%")
client = docker.from_env()
for container in client.containers.list():
print(f"{container.name}: {container.status}")
Агрегация метрик — собираем разрозненные данные в единую панель управления. Это основа любого мониторинга, от простых скриптов до сложных Prometheus + Grafana.
А какие команды вы первым делом запускаете при проблемах? Давайте составим список must-have команд для диагностики.
P.S. Этот скрипт — как стетоскоп для доктора: быстрая диагностика перед глубоким анализом.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2❤1