Перейти к содержанию
  • Лента
  • Категории
  • Последние
  • Метки
  • Популярные
  • Пользователи
  • Группы
Свернуть
exlends
Категории
  • ru
    Игры
    Образование
    Искусственный Интеллект
    Новости
    Бекенд, разработка серверов
    Фронтенд
    Мобильная разработка
    Языки программирования
    Разработка игр | 3D | 2D
    Базы данных
    CMS
    Системное Администрирование
    Операционные системы
    Программы
    Маркетинг
    Девайсы
    Юмор, Мемы

  • en
    Humor
    News
    AI
    Programming languages
    Frontend
    GameDev

  • Блоги

Авторизуйтесь, чтобы написать сообщение

  • Все категории
  • AladdinA
    Aladdin
    Полный гайд по работе с NodeBB CLI

    NodeBB поставляется с мощным командным интерфейсом (CLI), который позволяет выполнять различные операции без необходимости заходить в административную панель. Этот гайд охватывает все основные команды для управления форумом через терминал.

    Основные команды запуска

    Запуск и остановка форума

    Самые базовые команды для управления NodeBB:

    # Запустить форум
    ./nodebb start
    
    # Остановить форум
    ./nodebb stop
    
    # Альтернативный способ запуска
    npm start
    
    # Альтернативный способ остановки
    npm stop
    

    Просмотр логов

    # Просмотреть вывод сервера
    ./nodebb log
    
    # Просмотреть вывод в реальном времени
    ./nodebb start -l
    

    Режим разработки

    Режим разработки удобен при разработке и отладке:

    # Запустить в режиме разработки с логами
    ./nodebb dev
    
    # Эквивалентное написание
    ./nodebb start -dl
    

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

    Работа с плагинами

    Установка плагинов

    Через админ-панель (рекомендуется)

    Перейдите в Extend → Plugins и используйте встроенный магазин плагинов. Это гарантирует совместимость с вашей версией NodeBB.

    Через командную строку

    # Установить плагин через npm
    npm install nodebb-plugin-someplugin
    
    # После установки требуется пересборка
    ./nodebb build
    

    Важно: Устанавливайте через npm только те плагины, которые вы уверены совместимы с вашей версией NodeBB. Несовместимые плагины могут привести к краху форума.

    Активация плагина

    # Активировать плагин для следующего запуска
    # (префикс nodebb-plugin- опционален)
    ./nodebb activate nodebb-plugin-someplugin
    
    # Или без префикса
    ./nodebb activate someplugin
    
    # Если вы не знаете точное имя
    ./nodebb activate
    # Команда запросит выбрать плагин из доступных
    

    После активации нужно перезагрузить форум:

    ./nodebb start
    

    Деактивация плагина

    Если форум работает нормально

    # Деактивировать через админ-панель: Extend → Plugins → Деактивировать
    # Или через CLI:
    
    ./nodebb reset -p nodebb-plugin-someplugin
    
    # Можно использовать с или без префикса
    ./nodebb reset -p someplugin
    

    Если форум упал из-за несовместимого плагина

    # Деактивировать конкретный проблемный плагин
    ./nodebb reset -p nodebb-plugin-broken-plugin
    
    # Деактивировать ВСЕ плагины сразу
    ./nodebb reset -p
    
    # Затем перезагрузить форум
    ./nodebb start
    

    Просмотр установленных плагинов

    # Вывести список всех установленных плагинов
    ./nodebb plugins
    

    Команда покажет все плагины в node_modules, их статусы и версии.

    Работа с активами (CSS, JS, шаблоны)

    Полная пересборка активов

    # Собрать ВСЕ активы (JS, CSS, шаблоны, языки)
    ./nodebb build
    
    # Просмотреть все доступные опции сборки
    ./nodebb build -h
    

    Выборочная пересборка активов

    Если вы вносите изменения только в определенные части, можно собирать избирательно:

    # Собрать только административные JS и CSS, плюс шаблоны
    ./nodebb build adminjs admincss tpl
    
    # Доступные целевые системы:
    # - js          : клиентский JavaScript
    # - css         : клиентский CSS
    # - adminjs     : административный JavaScript
    # - admincss    : административный CSS
    # - tpl         : шаблоны
    # - lang        : языковые файлы
    # - public      : публичные статические файлы плагинов
    

    Пересборка в режиме разработки

    # Собрать активы в режиме разработки (без минификации)
    ./nodebb build -d
    

    Это ускорит сборку и упростит отладку.

    Управление пользователями

    Сброс пароля администратора

    # UID администратора в стандартной установке обычно 1
    ./nodebb user reset 1 --password новый-пароль
    
    # Если вы не знаете UID, найдите его в БД или админ-панели
    

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

    # Справка по командам управления пользователями
    ./nodebb user --help
    

    Сброс и переинициализация

    Полный сброс форума

    # Сбросить конфигурацию, вернуться к начальному состоянию
    ./nodebb reset
    

    Внимание: Эта команда очищает серьезные данные. Используйте с осторожностью.

    Сброс конкретного параметра

    # Сбросить все плагины
    ./nodebb reset -p
    
    # Сбросить конкретный плагин
    ./nodebb reset -p nodebb-plugin-someplugin
    
    # Сбросить темы
    ./nodebb reset -t
    
    # Сбросить настройки
    ./nodebb reset -s
    

    Получение информации о системе

    Вывести информацию о форуме

    # Показать версию NodeBB, информацию о ПК, конфигурацию БД и т.д.
    ./nodebb info
    

    Просмотр административных событий

    # Вывести последние 10 административных событий
    ./nodebb events 10
    
    # Вывести последние 50 событий
    ./nodebb events 50
    

    Обновление NodeBB

    Проверка обновлений

    # Запустить скрипты обновления и обновить пакеты
    ./nodebb upgrade
    
    # Запустить конкретный скрипт обновления
    ./nodebb upgrade скрипт-имя
    

    Установка форума

    Первоначальная настройка

    # Запустить мастер установки (только для новой установки)
    ./nodebb setup
    
    # Ответьте на вопросы конфигурации и установка завершится автоматически
    

    Справка и помощь

    Получить справку по любой команде

    # Общая справка
    ./nodebb help
    
    # Справка по конкретной команде
    ./nodebb help build
    ./nodebb help reset
    ./nodebb help plugins
    

    Полный список команд

    ./nodebb --help
    

    Полезные сценарии работы

    Сценарий 1: Установка нового плагина

    # Шаг 1: Остановить форум
    ./nodebb stop
    
    # Шаг 2: Установить плагин
    npm install nodebb-plugin-myawesomeplugin
    
    # Шаг 3: Пересобрать активы
    ./nodebb build
    
    # Шаг 4: Активировать плагин
    ./nodebb activate myawesomeplugin
    
    # Шаг 5: Запустить форум
    ./nodebb start
    
    # Шаг 6: Проверить логи на ошибки
    ./nodebb log
    

    Сценарий 2: Отладка проблемы с плагином

    # Запустить в режиме разработки для подробного логирования
    ./nodebb dev
    
    # Активировать плагин через админ-панель
    
    # Проверить логи на ошибки
    
    # Если форум упал, остановить его
    # Ctrl+C
    
    # Деактивировать проблемный плагин
    ./nodebb reset -p имя-плагина
    
    # Перезапустить в нормальном режиме
    ./nodebb start
    

    Сценарий 3: Восстановление форума от запертого плагина

    # Если форум не запускается из-за несовместимого плагина:
    
    # Деактивировать все плагины
    ./nodebb reset -p
    
    # Запустить форум
    ./nodebb start
    
    # Активировать плагины по одному через админ-панель, проверяя стабильность
    
    # Найти виноватый плагин и удалить его
    npm uninstall nodebb-plugin-bad-plugin
    
    # Пересобрать
    ./nodebb build
    

    Сценарий 4: Обновление после установки плагина

    # После добавления/удаления плагинов
    
    # Полная пересборка всех активов
    ./nodebb build
    
    # Перезагрузить форум
    ./nodebb stop
    ./nodebb start
    

    Работа с конфигурационными файлами

    Указать альтернативный конфиг-файл

    # Использовать не стандартный config.json
    ./nodebb start --config /path/to/config.json
    
    # Форматировать вывод логов в JSON
    ./nodebb start --json-logging
    
    # Установить уровень логирования
    ./nodebb start --log-level debug
    

    Лучшие практики

    ✅ Делайте:

    • Устанавливайте плагины через админ-панель, когда это возможно
    • Всегда выполняйте ./nodebb build после установки плагинов через npm
    • Используйте режим разработки (./nodebb dev) для отладки
    • Проверяйте логи при возникновении проблем
    • Делайте резервные копии перед установкой неизвестных плагинов

    ❌ Не делайте:

    • Не устанавливайте неопроверенные плагины на продакшене
    • Не удаляйте плагины вручную из node_modules - используйте npm uninstall
    • Не игнорируйте ошибки сборки активов
    • Не запускайте reset -p без необходимости на продакшене

    Освоение CLI NodeBB дает вам полный контроль над форумом и позволяет быстро решать проблемы. Регулярно практикуйтесь с этими командами, и вы станете опытным администратором NodeBB!

    Помните: всегда проверяйте справку команды перед использованием, используя ./nodebb help <команда>.


    1 0 2 Ответить
  • robloxR
    roblox
    Как написать первую программу для роблокс?

    1374348c-125d-40be-bba3-848f352d4a13-image.png

    Первая программа в Roblox пишется на языке Lua в среде Roblox Studio и обычно представляет собой простой скрипт, выводящий сообщение или меняющий свойства объекта. Это требует установки Roblox Studio, создания объекта и добавления скрипта с базовым кодом. Начните с шаблона Baseplate для простоты

    Установка Roblox Studio

    Скачайте Roblox Studio бесплатно с официального сайта Roblox Creator Hub после регистрации аккаунта. Запустите программу и создайте новое место через Create → New Place, выбрав базовый шаблон Baseplate.​

    Создание первого скрипта
    В панели Explorer (включите через View → Explorer) добавьте объект: нажмите + в Workspace, выберите Part (куб). Правой кнопкой мыши на объекте → Insert Object → Script. Дважды кликните скрипт для редактирования.

    Пример простого кода

    Замените содержимое скрипта на:

    print("Hello, Roblox World!")
    

    Нажмите Play, откройте Output (View → Output) — увидите сообщение в консоли. Для интерактивности используйте:

    local part = script.Parent
    part.Touched:Connect(function(hit)
        local player = game.Players:GetPlayerFromCharacter(hit.Parent)
        if player then
            part.BrickColor = BrickColor.Random()
            print(player.Name .. " коснулся объекта!")
        end
    end)
    

    Этот код меняет цвет объекта при касании игроком.

    Запустите тест через Play, проверьте изменения и ошибки в Output. Изучите Roblox Developer Hub для API, экспериментируйте с событиями вроде Touched и используйте wait() в циклах для производительности.


    2 0 1 Ответить
  • hannadevH
    hannadev
    Как сделать рабочие часы для скрипта

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

    У нас имеется скрипт который выполняет какие-то действия скажем постоянно 24/7 и нам необходимо сделать так что бы он к примеру работал с 8 утра до 17 вечера.

    Вот наша часть которая запускает нашу функцию чего-либо:

    setInterval(() => {
        periodicTask(arr).catch((err) => {
          console.error(`Ошибка блин...`, err);
        });
      }, intervalMs);
    

    Надеюсь всем понятно что intervalMs - это интервал с какой периодичностью необходимо выполнять действия.

    А теперь переходим к самому интересному, вот у нас есть функция periodicTask и нам нужно как-то откладывать задачи которые setInterval() на поставляет.

    Самый важный момент это то что сам setInterval() продолжает работать и не уходит в какую либо спячку дожидаться своего времени.

    Да можно было бы нагромоздить логики по типу “значит когда задача будет выполняться в интервале часов 16-17 мы меняет интервал на несколько часов” и т.п.

    Лишне все это лишнее! Заболталась что-то, продолжаем. Для дальнейшего нам потребуется сделать функцию нашего рабочего времени:

    function workingHours(data = new Date()) {
        const hour = data.getHours();
        return hour >= 8 && hour < 17;
    }
    

    Понятно? Если нет то - мы в функции берем объект Date забираем от туда текущий час и возвращает наше будущее условие.

    Теперь возвращаемся к нашей функции periodicTask:

    function periodicTask(arr) {
    
        if (!workingHours()) {
            console.log("Рабочий день уже закончился, не буду ничего делать!");
            return;
        }
    
        // Ну а дальше логика нашей задачи
    
    }
    

    Понятно!!!??? Мы в самом начале функции periodicTask ставим условие для проверки времени рабочих часов, и если сейчас не рабочее время то workingHours() вернет нам false, в следствии чего вернет return и ничего не произойдет.

    Еще раз для закрепления - return не останавливает интервал, он всего лишь завершает текущий вызов periodicTask!!!
    Сам интервал живет отдельно, как и выше писала можно заморочиться нагромоздить всякой ерунды, но это более чем рабочий вариант.

    p.s. С Ханной все сложное - легко, и да возьмите уже на работу 😥


    11 0 2 Ответить
  • kirilljsxK
    kirilljsx
    Albion Online для соло-фарма на мага PvE

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

    И так 1 и самое необходимое это конечно же оружие, и тут на необходим “Пылающий посох”, его можно как создать самому либо купить на аукциона примерно на за 40-50к серебра.
    Очень большой урон по площади, мобов будите выносить только в путь.

    780a1902-9d52-4fa7-a836-c1b72cc1f411-image.png


    Далее шапка, тут нам нужен “Колпак друида”:
    За счет этого колпака мы восстанавливаем себе манну и пассивку на время чтения заклинаний.
    d555b6f8-d37b-4938-88ca-54be44887dcf-image.png


    Теперь переходим к мантии, я лично использую “Королевская мантия”, за счет последнего заклинания увеличивается магический урони и исцеление, очень сильно помогает в подземельях, пассивку берем на время чтения заклинаний:

    1d37b991-245f-4cba-bf7d-14d57bb62cab-image.png


    Ботинки куда же без них, берем конечно “Сандалии ученого”, берем из заклинаний конечно кувырок, а из пасивки время чтения заклинаний.

    b64924f3-1ce2-4c86-be5f-af6ad08d4366-image.png

    И того в совокупности у нас получается аж -20% времени кастования заклинаний 😨


    Из накидок я предпочитаю “Lymhurst”, она активирует заклинание когда мана становится меньше < 40 восстанавливает до 70%.

    d510b4fa-f877-48e7-9901-a5513d4e0b81-image.png


    С таким не хитрых набором можно спокойно играть в соло-фарм, а то и даже в PVP не уступать другим игрокам.


    1 0 1 Ответить
  • kirilljsxK
    kirilljsx
    Как закрыть ссылку от индексации

    Даже не знаю куда отнести эту тему к разработке или все же к маркетингу? Да и черт с ним, давайте приступать.

    Чтобы закрыть ссылку от индексации, по сути, нужно дать роботам понятный сигнал: «эту конкретную ссылку не учитывай / не передавай по ней вес» или «не индексируй содержимое, где она стоит».


    Базовая теория: что именно «закрываем»

    Когда говорим «закрыть ссылку от индексации», обычно имеем в виду одно из двух:

    • не передавать по ней ссылочный вес (link juice);​
    • минимизировать ее влияние в поиске, чтобы роботы не «расценивали» ее как рекомендацию.

    Важно понимать:

    • robots.txt управляет доступом к URL, но не отдельной ссылкой в коде.​
    • Для конкретной ссылки используются атрибуты и разметка на странице (rel, meta robots, noindex-блоки и т.п.).

    Далее поговорим о способах.


    Способ 1

    Самый прямой и понятный способ - повесить на ссылку атрибут rel=“nofollow”.

    Пример верстки:

    <a href="https://example.com" rel="nofollow">Нужная, но не SEO-шная ссылка</a>
    

    Что это нам дает:

    • Робот видит ссылку, но получает явный сигнал не передавать по ней вес и не учитывать как голос доверия.​
    • В современных реалиях поисковики могут трактовать nofollow как рекомендацию, а не жёсткий запрет, но для «расчёта веса» этого достаточно в 99% задач.

    Когда так делаю у себя:

    • крауд / партнёрские / рекламные ссылки;
    • ссылки в комментариях, профилях, сигнатурах;
    • всё, что «должно быть кликабельным для людей, но не для SEO».

    Способ 2

    Если задача не в одной ссылке, а в целом блоке / странице, можно просто сказать роботам: «по ссылкам тут не ходи».​

    В <head> страницы:

    <meta name="robots" content="index, nofollow">
    

    Варианты:​
    index, follow – индексируем страницу и ссылки (режим по умолчанию).
    noindex, follow – контент не индексировать, по ссылкам ходить.
    index, nofollow – контент индексировать, по ссылкам не ходить / не учитывать.
    noindex, nofollow – полностью выключили и контент, и ссылки.

    Такой подход логичен, если:

    • страница служебная: фильтры, поиск, корзина и т.п.;​
    • там много технических ссылок, которые не надо ни ранжировать, ни учитывать.

    Хотя отфилитрованные страницы иногда полезны для seo если вы продвигаете товар!


    Способ 3

    Если нужно закрыть кусок контента, где находится ссылка, а страницу в целом оставлять, можно использовать специальные блоки:

    • для Яндекса и некоторых CMS — комментарий <!--noindex--> ... <!--/noindex-->;​
    • для Google — он официально опирается на meta / HTTP-заголовок, но на практике многие используют комбинированные схемы (noindex + nofollow).

    Условный пример:

    <!--noindex-->
        <a href="https://example.com">Ссылка, которую не хочу светить в индексе</a>
    <!--/noindex-->
    

    Идея простая:

    • поисковик не должен использовать этот фрагмент для ранжирования, а ссылки внутри не должны играть SEO-ролей.​

    Я обычно комбинирую: noindex-блок + rel=“nofollow” для надёжности, если речь про чувствительные ссылки.


    Способ 4 (не лучший)

    Если хочется, чтобы ссылка вообще не присутствовала как <a> в исходнике, а рисовалась уже на клиенте, можно собирать ее через JavaScript.

    Схема:

    • В HTML вместо ссылки — пустой контейнер.
    • Отдельный .js файл генерирует и вставляет в DOM.
    • Этот .js закрывается в robots.txt

    Пример HTML:

    <div class="js-link-placeholder" data-href="https://example.com" data-text="Скрытая ссылка"></div>
    <script src="/static/js/links.js"></script>
    

    В robots.txt:

    User-agent: *
    Disallow: /static/js/links.js
    

    С точки зрения робота:

    • в исходном HTML нет <a href="...">, а скрипт, который рисует ссылку, ему «официально» смотреть нельзя.​
    • это один из самых жёстких методов скрытия внешних ссылок именно от поисковиков, но он сложнее в поддержке.

    Способ 5

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

    Основные варианты:

    • meta robots noindex, nofollow в `:
      <meta name="robots" content="noindex, nofollow">
      
    • директивы X-Robots-Tag в HTTP-заголовке (на уровне сервера)
    • запрет в robots.txt + при необходимости авторизация (пароль через .htaccess).

    Это уместно, если страница техническая / временная, там много мусорных ссылок или проще «вырубить» все, чем точечно чистить.


    Вот такие есть способы закрыть ссылки/страницы или блок от индексации. Пользуйтесь!
    И конечно оставайтесь с нами на связи:

    https://t.me/exlends


    2 0 1 Ответить
  • kirilljsxK
    kirilljsx
    Сколько игроков насчитывается в League of Legends (LoL)а сколько в Dota 2?

    В League of Legends (LoL) насчитывается около 120–135 миллионов ежемесячных активных игроков (MAU), с пиковым одновременным онлайном в 1–1,08 миллиона человек. В Dota 2 показатели ниже: примерно 600 тысяч средних одновременных игроков за последние 30 дней, с пиком до 800–900 тысяч.

    Сравнение метрик

    Метрика League of Legends Dota 2
    Ежемесячные активные (MAU) 120–135 млн ~1 млн (оценка)​
    Средний одновременный онлайн ~1 млн 500–600 тыс.
    Пиковый одновременный 1,08–14 млн (исторический)​ 800–910 тыс.​

    Факторы влияния

    LoL лидирует благодаря китайскому рынку и кросс-платформенности, в то время как Dota 2 ограничена Steam с пиками во время турниров вроде The International. Данные на декабрь 2025 года приблизительны, так как Riot не публикует официальную статистику, а Valve показывает только Steam-онлайн.


    3 0 1 Ответить
  • AladdinA
    Aladdin
    Деплой next js проекта после сборки

    Конфигурационный файл

    #!/bin/bash
    # Конфигурация деплоя
    
    # Данные сервера
    SERVER_USER="..."
    SERVER_IP="..."
    SERVER_PROJECT_PATH="..."
    PM2_SERVICE_NAME="..."
    NODE_PATH="~/.nvm/versions/node/v25.0.0/bin/"
    
    

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

    сам скрипт

    CONFIG_FILE - указываем путь к конфигурационому файу!!!

    
    
    #!/bin/bash
    
    # ============================================
    # ЦВЕТА И СТИЛИ
    # ============================================
    
    RESET='\033[0m'
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    BLUE='\033[0;34m'
    CYAN='\033[0;36m'
    BOLD='\033[1m'
    
    # ============================================
    # ПРОСТЫЕ ФУНКЦИИ ВЫВОДА
    # ============================================
    
    log() {
        echo -e "[$(date '+%H:%M:%S')] $1"
    }
    
    success() {
        echo -e "${GREEN}✓ $1${RESET}"
    }
    
    error() {
        echo -e "${RED}✗ $1${RESET}"
    }
    
    info() {
        echo -e "${CYAN}→ $1${RESET}"
    }
    
    title() {
        echo -e "\n${BLUE}${BOLD}=== $1 ===${RESET}"
    }
    
    
    # ============================================
    # ФУНКЦИИ ДЛЯ ИЗМЕРЕНИЯ ВРЕМЕНИ
    # ============================================
    
    start_timer() {
        SCRIPT_START_TIME=$(date +%s)
        SCRIPT_START_TIME_MS=$(date +%s%3N)
    }
    
    get_elapsed_time() {
        local current_time=$(date +%s)
        local elapsed=$((current_time - SCRIPT_START_TIME))
    
        local hours=$((elapsed / 3600))
        local minutes=$(((elapsed % 3600) / 60))
        local seconds=$((elapsed % 60))
    
        if [ $hours -gt 0 ]; then
            printf "%02d:%02d:%02d" $hours $minutes $seconds
        elif [ $minutes -gt 0 ]; then
            printf "%02d:%02d" $minutes $seconds
        else
            printf "%d сек" $seconds
        fi
    }
    
    get_elapsed_time_ms() {
        local current_time_ms=$(date +%s%3N)
        local elapsed_ms=$((current_time_ms - SCRIPT_START_TIME_MS))
    
        local seconds=$((elapsed_ms / 1000))
        local ms=$((elapsed_ms % 1000))
    
        if [ $seconds -gt 0 ]; then
            printf "%d.%03d сек" $seconds $ms
        else
            printf "%d мс" $ms
        fi
    }
    
    # Начинаем отсчет времени выполнения скрипта
    start_timer
    
    # ============================================
    # ЗАГРУЗКА КОНФИГУРАЦИИ
    # ============================================
    
    title "Деплой проекта"
    
    CONFIG_FILE="..."
    
    info "Загрузка конфигурации..."
    
    if [ -f "$CONFIG_FILE" ]; then
        source "$CONFIG_FILE"
        success "Конфигурация загружена"
    else
        error "Файл конфигурации не найден: $CONFIG_FILE"
        exit 1
    fi
    
    # Проверяем обязательные параметры
    if [ -z "$SERVER_USER" ] || [ -z "$SERVER_IP" ] || [ -z "$SERVER_PROJECT_PATH" ]; then
        error "В конфигурации не указаны обязательные параметры"
        exit 1
    fi
    
    # ============================================
    # ИНФОРМАЦИЯ О ДЕПЛОЕ
    # ============================================
    
    info "Настройки деплоя:"
    echo "  Пользователь: $SERVER_USER"
    echo "  Сервер: $SERVER_IP"
    echo "  Путь проекта: $SERVER_PROJECT_PATH"
    echo "  Название сервиса PM2: $PM2_SERVICE_NAME"
    echo "  Путь node: $NODE_PATH"
    
    # ============================================
    # ЗАГРУЗКА ФАЙЛОВ
    # ============================================
    
    title "Загрузка файлов на сервер"
    
    FILES_TO_UPLOAD=(
        ".env.local"
        ".env"
        "./package.json"
        "./package-lock.json"
        "./public"
        ".next"
        "./node_modules"
    )
    
    RSYNC_CMD="rsync -arvpz --progress --delete ${FILES_TO_UPLOAD[@]} $SERVER_USER@$SERVER_IP:$SERVER_PROJECT_PATH"
    
    log "Выполняю загрузку файлов..."
    
    UPLOAD_START_TIME=$(date +%s)
    if $RSYNC_CMD; then
        UPLOAD_END_TIME=$(date +%s)
        UPLOAD_DURATION=$((UPLOAD_END_TIME - UPLOAD_START_TIME))
        success "Файлы успешно загружены ($UPLOAD_DURATION сек)"
    else
        error "Ошибка при загрузке файлов"
        exit 1
    fi
    
    # ============================================
    # ПЕРЕЗАПУСК ПРИЛОЖЕНИЯ
    # ============================================
    
    title "Перезапуск приложения"
    
    if [ -z "$PM2_SERVICE_NAME" ]; then
        info "PM2_SERVICE_NAME не указан, пропускаю перезапуск"
    else
        RESTART_CMD="$NODE_PATH/node $NODE_PATH/pm2 restart $PM2_SERVICE_NAME"
        log "Выполняю перезапуск..."
    
        RESTART_START_TIME=$(date +%s)
        if ssh $SERVER_USER@$SERVER_IP " $RESTART_CMD"; then
            RESTART_END_TIME=$(date +%s)
            RESTART_DURATION=$((RESTART_END_TIME - RESTART_START_TIME))
            success "Приложение успешно перезапущено ($RESTART_DURATION сек)"
        else
            error "Ошибка при перезапуске приложения"
            exit 1
        fi
    fi
    
    # ============================================
    # ЗАВЕРШЕНИЕ
    # ============================================
    
    title "Деплой завершен успешно"
    
    echo -e "${GREEN}${BOLD}✅ Все задачи выполнены!${RESET}"
    echo "Время начала: $(date -d @$SCRIPT_START_TIME '+%H:%M:%S')"
    echo "Время завершения: $(date '+%H:%M:%S')"
    echo "Общее время выполнения: $(get_elapsed_time)"
    echo "Сервер: $SERVER_IP"
    
    echo -e "${YELLOW}📊 Статистика выполнения:${RESET}"
    echo "Загрузка файлов: $UPLOAD_DURATION сек"
    if [ ! -z "$RESTART_DURATION" ]; then
        echo "Перезапуск приложения: $RESTART_DURATION сек"
    fi
    echo "Итого: $(get_elapsed_time)"
    
    exit 0
    
    
    

    2 0 3 Ответить
  • kirilljsxK
    kirilljsx
    Хакеры взломали одного из ключевых разработчиков реестра воинского учёта в России

    f96a844d-ac5b-45f9-9a06-dfbfdf64cbae-image.png

    Хакеры заявляют, что стерли все данные. Сейчас сайт лежит наглухо

    А что из этого следует? Правильно! В свете последних событий таких как уязвимость в NextJs - специализация кибер-безопасника станет ой как востребованной.

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

    И вот как раз совет новичкам, хотите в ИТ? Идите в кибербез или devops))


    upd. Появившиеся в сети Интернет вбросы о якобы взломе Реестра воинского учета не соответствуют действительности, сообщило Минобороны…
    И на сайте весит вот такая плашка:

    95239b2e-22a1-49f8-9b9f-a087dde7dc83-image.png


    7 0 1 Ответить
  • kirilljsxK
    kirilljsx
    Расчет затрат на ИТ проект

    Я, как фулл-стек разработчик и владелец бизнеса с кучей своих проектов вроде и форумов, часто сталкиваюсь с вопросом от коллег по цеху и не только: сколько реально стоит IT-проект?

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

    Основные компоненты бюджета

    В каждом проекте затраты делятся на прямые и косвенные. Прямые - это зарплаты команды, лицензии, железо и конечно сейчас добавляются агрегаторы ИИ (Кстати я рекомендую Polza).
    Косвенные - офис, налоги, командировки. Я всегда начинаю с декомпозиции задач: бью проект на этапы вроде анализа, кодинга и тестов.​

    • Анализ требований: 5-10% бюджета, собираем ТЗ.

    • Разработка: самая жирная часть, до 40-50%.

    • Тестирование и внедрение: 15-20% каждая.

    • Поддержка: 10%, но растягивается на годы.

    • Инфраструктура: сервера, облака - 10-15%.​

    • Не забывайте риски - добавляю 20-30% на “сюрпризы” от клиента.

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

    Формула расчета

    Я использую простую формулу: Стоимость = (Часы специалиста × Ставка часа) + Материалы + Риски. Ставка в России в среднем - от 1700 руб/час для мидла, но для клиента можно умножить на 4. Или беру модели вроде COCOMO II для точности: она считает трудозатраты по размеру кода и сложности.

    Для примера: команда из 4 человек на 3 месяца (10-16 недель) по рыночной ставке - вилка 1-2 млн руб.

    Пример таблицы затрат

    Вот как выглядит моя смета для типичного проекта на 1,5 млн руб (на основе реальных оценок):

    66d6f22e-a9f1-4623-af7b-dcfbb8966d87-image.png
    Распределение типичных затрат в IT-проекте (отдельное спасибо perplexity.ai за генерацию изображения таблицы)

    Этап Часы Ставка руб/час Затраты руб Риски 20%
    Анализ требований 500 2000 1 000 000 200 000
    Разработка 1500 2500 3 750 000 750 000
    Тестирование 800 2000 1 600 000 320 000
    Внедрение 1200 2200 2 640 000 528 000
    Поддержка 500 1800 900 000 180 000
    Итого 4500 - 10 000 000 2 000 000

    Распределение типичных затрат в IT-проекте.


    Методы для точности

    Для сложных проектов юзаю экспертную оценку или аналогии - смотрю похожие кейсы на vc. ru или Habr. В Битрикс24 автоматизирую: ресурсный план + косвенные расходы. Agile помогает корректировать на лету, без жесткого водопада.​

    В итоге, бюджет - это не угадывание, а план с запасом. Если проект для B2B вроде моих investsteel.ru и profitsteel.ru, добавляю на интеграции или обслуживание +30%. Пробуйте сами, и не забудьте про окупаемость!

    p.s. Наши труды как разработчиков на самом деле далеко как недооценены! А вклад по маркетингу может быть гораздо больше! Если клиент хочет результат - пусть платит.


    0 0 1 Ответить
  • kirilljsxK
    kirilljsx
    Web Push API: Полное руководство по реализации push-уведомлений

    Присаживайте по удобнее, заварите чайку тема будет объемная и большая…

    А делиться я буду своим опытом реализации push-уведомлений на веб-приложениях с помощью Web Push API. Это настоящий киллер-фича, который позволяет держать пользователей в курсе даже тогда, когда они не находятся на вашем сайте. Давайте разберемся с этой технологией во всех деталях.


    Но давайте с самого начала - Что такое Web Push API и зачем это нужно?

    Web Push API - это стандартный API браузера, который позволяет отправлять пользователям нативные push-уведомления, не полагаясь на сторонние сервисы вроде Firebase или Pusher. Звучит просто? На самом деле это мощная технология, которая работает прямо из коробки во всех современных браузерах.

    Главные преимущества:

    • Пользователи получают уведомления даже когда браузер закрыт (на большинстве платформ)
    • Не нужно платить за сторонние сервисы - все работает через браузер
    • Полный контроль над данными и шифрованием
    • Стандартизированный подход, поддерживаемый всеми современными браузерами

    И да на нашем форум тоже есть пуш-уведомления, но мы их пока дорабатываем 🙂

    Архитектура Web Push API: из чего всё состоит?

    Помимо самой практики не менее важно понимать что вы делает и как это работает. Да-да, вайб-кодеры читайте внимательнее и вникайте! Я что просто так собирал этот материал? 😀

    Вся система состоит из четырех ключевых компонентов:

    1. Service Worker - Фоновый боец приложения
    Service Worker - это JavaScript-скрипт, который работает в фоне, отдельно от основной страницы. Именно благодаря ему уведомления доходят до пользователя даже при закрытом браузере.

    Что делает Service Worker:

    • Получает push-события от браузера
    • Обрабатывает входящие уведомления
    • Отображает уведомления пользователю
    • Обрабатывает клики по уведомлениям

    2. VAPID ключи - Паспорт вашего сервера
    VAPID (Voluntary Application Server Identification) - это криптографическая пара ключей, которая идентифицирует ваш сервер при отправке уведомлений. Думайте о ней как о паспорте: без него push-сервис браузера просто откажется принимать ваши уведомления.​

    Как это работает:

    • Публичный ключ передаётся браузеру
    • Приватный ключ хранится на вашем сервере и никогда не передаётся
    • Сервер подписывает все отправляемые уведомления приватным ключом
    • Push-сервис браузера проверяет подпись с помощью публичного ключа

    3. Subscription Object - Билет на доставку
    После подписки пользователя, вы получаете объект, который содержит:​

    • Endpoint - уникальный URL для отправки сообщений
    • Ключи шифрования (p256dh и auth) - для безопасности данных
    • Это всё хранится в вашей базе данных и используется для отправки уведомлений

    4. Push Service - Почтальон браузера
    Это сервис, управляемый браузером (Google FCM для Chrome, Mozilla для Firefox и т.д.), который:

    • Получает шифрованные сообщения от вашего сервера
    • Хранит их, если пользователь оффлайн
    • Доставляет их браузеру пользователя
    • Гарантирует доставку даже при нестабильном соединении

    Пошаговая реализация

    Хватит теории, давайте перейдем к практике, подготовьте свои редакторы, проверьте работает ли нода и начинаем ковыряться.

    Шаг 1: Генерируем VAPID ключи

    Первое, что нужно сделать - это сгенерировать пару VAPID ключей. Это одноразовая операция для вашего приложения.

    Установим web-push:

    npm install web-push
    

    Генерируем ключи:

    npx web-push generate-vapid-keys
    

    Вывод будет примерно таким:

    Public Key: BKV3l...qA
    Private Key: 1Ik0...xA
    

    Сохраните эти ключи в переменных окружения! Например, в .env:

    VAPID_PUBLIC_KEY=BKV3l...qA
    VAPID_PRIVATE_KEY=1Ik0...xA
    

    Шаг 2: Регистрируем Service Worker

    Теперь создаем файл Service Worker и регистрируем его на клиенте.

    Файл public/sw.js (Service Worker):

    // Прослушиваем входящие push-события
    self.addEventListener('push', event => {
      // Если сообщение содержит данные, парсим их
      const data = event.data?.json() || {};
      
      // Подготавливаем параметры уведомления
      const options = {
        body: data.body || 'Новое уведомление',
        icon: data.icon || '/icon-192.png',
        badge: '/badge.png',
        vibrate: [200, 100, 200], // Вибрация на мобильных
        tag: data.tag || 'notification', // Теги для группировки
        data: { 
          url: data.url || '/' // URL для открытия по клику
        }
      };
      
      // Отображаем уведомление
      event.waitUntil(
        self.registration.showNotification(
          data.title || 'Уведомление',
          options
        )
      );
    });
    
    // Обрабатываем клик по уведомлению
    self.addEventListener('notificationclick', event => {
      // Закрываем уведомление
      event.notification.close();
      
      // Открываем URL в окне браузера
      event.waitUntil(
        clients.matchAll({ type: 'window' })
          .then(clientList => {
            // Проверяем, не открыт ли уже этот URL
            for (const client of clientList) {
              if (client.url === event.notification.data?.url && 'focus' in client) {
                return client.focus();
              }
            }
            // Если не открыт, открываем новое окно
            if (clients.openWindow) {
              return clients.openWindow(event.notification.data?.url || '/');
            }
          })
      );
    });
    
    // Обработчик закрытия уведомления
    self.addEventListener('notificationclose', event => {
      console.log('Уведомление было закрыто', event.notification.tag);
    });
    

    На клиенте (например, в index.html или вашем React/Vue компоненте):

    // Проверяем поддержку API
    if ('serviceWorker' in navigator && 'PushManager' in window) {
      // Регистрируем Service Worker при загрузке страницы
      navigator.serviceWorker.register('/sw.js')
        .then(registration => {
          console.log('✅ Service Worker зарегистрирован:', registration);
        })
        .catch(error => {
          console.error('❌ Ошибка регистрации Service Worker:', error);
        });
    } else {
      console.warn('⚠️ Web Push API не поддерживается в этом браузере');
    }
    

    Шаг 3: Запрашиваем разрешение и подписываем пользователя

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

    // Публичный VAPID ключ (не секретный, можно хранить в коде)
    const VAPID_PUBLIC_KEY = process.env.REACT_APP_VAPID_PUBLIC_KEY;
    
    // Вспомогательная функция для конвертации Base64 в Uint8Array
    function urlBase64ToUint8Array(base64String) {
      const padding = '='.repeat((4 - base64String.length % 4) % 4);
      const base64 = (base64String + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/');
      
      const rawData = window.atob(base64);
      const outputArray = new Uint8Array(rawData.length);
      
      for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
      }
      
      return outputArray;
    }
    
    // Функция подписки на уведомления
    async function subscribeToPush() {
      try {
        // Ждём, пока Service Worker будет готов
        const registration = await navigator.serviceWorker.ready;
        
        // Запрашиваем разрешение у пользователя
        // ВАЖНО: это должно быть в ответ на действие пользователя (клик)
        const permission = await Notification.requestPermission();
        
        if (permission !== 'granted') {
          console.log('❌ Пользователь отклонил разрешение');
          return;
        }
        
        // Подписываем пользователя на push
        const subscription = await registration.pushManager.subscribe({
          userVisibleOnly: true, // Уведомления должны быть видны
          applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
        });
        
        console.log('✅ Пользователь подписан на уведомления');
        console.log('Subscription:', subscription);
        
        // Отправляем данные подписки на сервер
        await fetch('/api/subscribe', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(subscription)
        });
        
        console.log('✅ Данные подписки отправлены на сервер');
        
      } catch (error) {
        console.error('❌ Ошибка при подписке:', error);
      }
    }
    
    // Вешаем обработчик на кнопку
    document.getElementById('subscribe-btn').addEventListener('click', subscribeToPush);
    

    Лучшие практики для запроса разрешения:

    • Запрашивайте разрешение только после действия пользователя (клик на кнопку)
    • Объясните, зачем вам нужны уведомления
    • Не спрашивайте сразу же при загрузке страницы — это раздражает
    • Предложите возможность отписки позже

    Шаг 4: Серверная реализация (Node.js)

    На сервере используем библиотеку web-push для отправки уведомлений.

    Установка зависимостей:

    npm install web-push express dotenv
    

    Файл server.js:

    const express = require('express');
    const webpush = require('web-push');
    require('dotenv').config();
    
    const app = express();
    app.use(express.json());
    
    // Инициализируем web-push с VAPID ключами
    webpush.setVapidDetails(
      'mailto:your-email@example.com', // Ваша почта (может быть любой)
      process.env.VAPID_PUBLIC_KEY,
      process.env.VAPID_PRIVATE_KEY
    );
    
    // Хранилище подписок (в продакшене используйте БД)
    const subscriptions = new Set();
    
    // Эндпоинт для сохранения подписки пользователя
    app.post('/api/subscribe', (req, res) => {
      const subscription = req.body;
      
      try {
        // Сохраняем подписку (в реальном приложении — в БД)
        subscriptions.add(JSON.stringify(subscription));
        
        res.status(201).json({ 
          success: true, 
          message: 'Подписка сохранена' 
        });
      } catch (error) {
        console.error('Ошибка при сохранении подписки:', error);
        res.status(500).json({ 
          success: false, 
          message: 'Ошибка сохранения подписки' 
        });
      }
    });
    
    // Функция отправки уведомления конкретному пользователю
    async function sendNotificationToUser(subscription, payload) {
      try {
        await webpush.sendNotification(subscription, JSON.stringify(payload));
        console.log('✅ Уведомление отправлено');
      } catch (error) {
        if (error.statusCode === 410) {
          // Подписка устарела, удаляем её
          subscriptions.delete(JSON.stringify(subscription));
          console.log('⚠️ Подписка удалена (410 Gone)');
        } else {
          console.error('❌ Ошибка отправки:', error);
        }
      }
    }
    
    // Функция отправки уведомления всем пользователям
    async function sendNotificationToAll(payload) {
      console.log(`📤 Отправляем уведомление ${subscriptions.size} пользователям`);
      
      for (const subStr of subscriptions) {
        const subscription = JSON.parse(subStr);
        await sendNotificationToUser(subscription, payload);
      }
    }
    
    // Тестовый эндпоинт для отправки уведомления
    app.post('/api/send-notification', async (req, res) => {
      const { title, body, icon, url } = req.body;
      
      const payload = {
        title: title || 'Новое уведомление',
        body: body || 'Нажмите, чтобы узнать больше',
        icon: icon || '/icon-192.png',
        url: url || '/'
      };
      
      try {
        await sendNotificationToAll(payload);
        res.json({ 
          success: true, 
          message: `Уведомление отправлено ${subscriptions.size} пользователям` 
        });
      } catch (error) {
        console.error('Ошибка:', error);
        res.status(500).json({ 
          success: false, 
          message: 'Ошибка при отправке уведомления' 
        });
      }
    });
    
    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
      console.log(`🚀 Сервер запущен на http://localhost:${PORT}`);
    });
    

    Важные особенности для каждого браузера

    Ну и конечно же разные браузеры имеют разные требования и ограничения. Об этом нужно помнить!

    Браузер Требования Особенности
    Chrome/Edge Видимые уведомления для каждого push Поддержка до 4KB полезной нагрузки
    Firefox Видимые уведомления требуются 4KB полезная нагрузка
    Safari Видимые уведомления обязательны Только 2KB полезная нагрузка, требует веб-приложения
    iOS (Safari) Не поддерживает background push Уведомления работают только при открытом приложении

    Всегда отображайте уведомления немедленно при их получении в Service Worker. Некоторые браузеры требуют это для сохранения разрешений.


    Ограничения на размер сообщений

    Push-уведомления не могут содержать большое количество данных. Если вам нужно отправить много информации:

    // ❌ ПЛОХО: отправляем большой JSON
    const badPayload = {
      title: 'Новая статья',
      body: 'Очень длинное описание статьи...',
      fullArticle: '...' // 100KB текста
    };
    
    // ✅ ХОРОШО: отправляем минимальные данные
    const goodPayload = {
      title: 'Новая статья',
      body: 'Кликните для прочтения',
      articleId: '12345' // ID статьи
    };
    
    // В Service Worker загружаем полные данные:
    self.addEventListener('push', async event => {
      const data = event.data?.json() || {};
      
      // Загружаем полные данные с сервера
      const response = await fetch(`/api/article/${data.articleId}`);
      const article = await response.json();
      
      // Теперь отображаем с полной информацией
      event.waitUntil(
        self.registration.showNotification(article.title, {
          body: article.description,
          image: article.coverImage
        })
      );
    });
    

    Управление жизненным циклом подписки

    Время от времени браузер может автоматически пересоздать подписку (например, после обновления Service Worker). Нужно об этом позаботиться:

    // В Service Worker
    self.addEventListener('pushsubscriptionchange', event => {
      console.log('⚠️ Подписка изменилась, переподписываемся');
      
      event.waitUntil(
        self.registration.pushManager.subscribe({
          userVisibleOnly: true,
          applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY)
        })
        .then(newSubscription => {
          // Отправляем новую подписку на сервер
          return fetch('/api/update-subscription', {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(newSubscription)
          });
        })
        .catch(error => {
          console.error('❌ Ошибка при переподписке:', error);
        })
      );
    });
    

    Безопасность: неотъемлемая часть

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

    А теперь о правилах, золотые правила:

    • Приватный ключ VAPID никогда не передавайте клиенту. Это секрет!

    • Шифруйте полезную нагрузку - Web Push API делает это автоматически, но всегда проверяйте

    • Валидируйте данные на сервере при сохранении подписок

    • Используйте HTTPS - это не опционально

    • Реализуйте Rate Limiting на эндпоинте отправки, чтобы предотвратить спам

    • Удаляйте старые подписки, если браузер возвращает ошибку 410 (Gone)

    Для примера:

    // Пример простой защиты на сервере
    const RATE_LIMIT = 5; // Максимум 5 уведомлений в минуту
    let lastNotificationTime = 0;
    let notificationCount = 0;
    
    app.post('/api/send-notification', (req, res) => {
      const now = Date.now();
      
      // Сбрасываем счётчик каждую минуту
      if (now - lastNotificationTime > 60000) {
        lastNotificationTime = now;
        notificationCount = 0;
      }
      
      // Проверяем лимит
      if (notificationCount >= RATE_LIMIT) {
        return res.status(429).json({ 
          success: false, 
          message: 'Слишком много запросов' 
        });
      }
      
      notificationCount++;
      
      // ... остальной код отправки
    });
    

    Также стоит упомянуть о типичных ошибках и как их решать:

    Проблема Причина Решение
    Service Worker не регистрируется Отсутствует HTTPS (кроме localhost) Используйте HTTPS или localhost для тестирования
    Браузер не запрашивает разрешение API вызван без действия пользователя Вешайте обработчик на клик кнопки
    Уведомления не приходят VAPID ключи неправильные Проверьте, что используете одну пару ключей везде
    Ошибка 410 (Gone) Подписка устарела Удалите подписку из БД, переподпишите пользователя
    Сообщение слишком большое Превышен лимит (4KB или 2KB) Уменьшите размер payload, отправляйте ID вместо данных

    Продвинутые фишки

    Возможно кому-то это покажется излишним, но в некоторых случаях такие решения могут облегчить жизнь.

    Группировка уведомлений
    Используйте параметр tag для группировки уведомлений одного типа:

    const options = {
      body: 'Новое сообщение в чате',
      tag: 'chat-messages', // Все уведомления с этим тегом объединяются
      renotify: true // Заново уведомить пользователя
    };
    

    Кнопки действий
    Некоторые браузеры поддерживают кнопки в уведомлениях:

    const options = {
      body: 'Вам пришел новый заказ',
      actions: [
        {
          action: 'approve',
          title: 'Принять'
        },
        {
          action: 'decline',
          title: 'Отклонить'
        }
      ]
    };
    
    // В Service Worker обрабатываем действия
    self.addEventListener('notificationclick', event => {
      if (event.action === 'approve') {
        // Логика для принятия
      } else if (event.action === 'decline') {
        // Логика для отклонения
      }
    });
    

    Изображения в уведомлениях
    Многие браузеры поддерживают изображения:

    const options = {
      body: 'Новая фотография',
      image: 'https://example.com/photo.jpg', // Большое изображение
      icon: '/app-icon.png', // Маленькая иконка
      badge: '/badge-icon.png' // Иконка для Android
    };
    

    Web Push API - это мощный инструмент, который позволяет создавать действительно стоящие веб-приложения. Да, есть нюансы с разными браузерами и ограничения на размер данных, но это все решаемо.​

    И помните золотые правила:

    ✅ Используйте HTTPS (или localhost для тестирования)
    ✅ Всегда запрашивайте разрешение в ответ на действие пользователя
    ✅ Генерируйте и сохраняйте VAPID ключи в безопасности
    ✅ Регулярно удаляйте устаревшие подписки (410 ошибки)
    ✅ Следите за размером payload — не более 4KB
    ✅ Тестируйте в DevTools для быстрой отладки

    Надеюсь, это руководство было исчерпывающим и поможет вам разобраться с Web Push API. Если у вас возникнут вопросы - добро пожаловать на наш форум.
    Не стесняйтесь и задавайте вопросы.

    И конечно удачи в разработке 😊 !


    0 0 1 Ответить
  • MugiwaraM
    Mugiwara
    Исчерпывающее руководство по конфигурации Nginx

    Nginx — это высокопроизводительный веб-сервер, обратный прокси-сервер и балансировщик нагрузки. Архитектура Nginx основана на модели master-процесса и worker-процессов. Master-процесс читает и обрабатывает конфигурацию, а также управляет worker-процессами, которые выполняют фактическую обработку запросов.

    Структура конфигурационного файла

    Конфигурация Nginx имеет иерархическую блочную структуру, где директивы организованы в контексты. Основной конфигурационный файл обычно находится по адресу /etc/nginx/nginx.conf.

    Основные контексты конфигурации

    Main (главный) контекст — глобальная область, содержащая директивы, которые влияют на весь экземпляр Nginx.

    Events контекст — управляет настройками обработки соединений.

    HTTP контекст — содержит всю HTTP-связанную конфигурацию.

    Server контекст — определяет виртуальные хосты и специфичные для сервера настройки.

    Location контекст — специфичная для URL конфигурация внутри server-блока.

    Upstream контекст — определяет группы серверов для балансировки нагрузки.

    Пример базовой структуры конфигурации

    # Main контекст - глобальные директивы
    user nginx;
    worker_processes auto;
    error_log /var/log/nginx/error.log;
    pid /var/run/nginx.pid;
    
    # Events контекст
    events {
        worker_connections 1024;
        use epoll;
    }
    
    # HTTP контекст
    http {
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
        
        # Server контекст
        server {
            listen 80;
            server_name example.com;
            root /var/www/html;
            
            # Location контекст
            location / {
                try_files $uri $uri/ =404;
            }
        }
    }
    

    Процессы и соединения

    Worker Processes

    Директива worker_processes определяет количество рабочих процессов для обработки запросов. Каждый worker-процесс является однопоточным.

    worker_processes auto;  # автоматически определяет количество CPU ядер
    

    Как это работает: Если Nginx выполняет CPU-интенсивные операции (например, SSL или gzip), и у вас 2 или более CPU/ядер, то вы можете установить worker_processes равным количеству CPU или ядер. Значение auto автоматически определяет оптимальное количество на основе доступных ядер.

    Worker Connections

    Директива worker_connections определяет максимальное количество одновременных соединений, которые может открыть один worker-процесс.

    events {
        worker_connections 1024;
    }
    

    Расчет максимального количества клиентов:

    max_clients = worker_processes × worker_connections

    Например, если у вас 4 worker-процесса и 1024 соединения на процесс, вы можете обслуживать до 4096 клиентов одновременно.

    Важное замечание: При использовании Nginx в качестве reverse proxy, это число нужно делить на 2, поскольку каждое клиентское соединение требует соединения с backend-сервером.

    Оптимизация worker_connections

    По умолчанию значение составляет 512 или 768. Вы можете увеличить это значение до лимита системы, определяемого командой ulimit -n.

    events {
        worker_connections 1024;
        use epoll;  # для Linux
        multi_accept on;  # принимать несколько соединений за один цикл событий
    }
    

    Директива Server

    Server-блок определяет виртуальный хост — конфигурацию для конкретного домена или IP-адреса.

    Listen директива

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

    server {
        listen 80;                    # IPv4 на порту 80
        listen [::]:80;               # IPv6 на порту 80
        listen 443 ssl http2;         # HTTPS с HTTP/2
        listen 192.168.1.10:8080;     # конкретный IP и порт
    }
    

    Server_name директива

    Определяет доменное имя (или имена), которые будут обрабатываться этим server-блоком.

    server {
        server_name example.com www.example.com;  # несколько доменов
    }
    
    server {
        server_name *.example.com;  # wildcard
    }
    
    server {
        server_name ~^(?<subdomain>.+)\.example\.com$;  # regex
    }
    

    Как Nginx выбирает server-блок:

    1. Точное совпадение имени
    2. Самое длинное совпадение, начинающееся с *
    3. Самое длинное совпадение, заканчивающееся на *
    4. Первое совпадение по регулярному выражению
    5. Default server (указанный с default_server в listen)

    Директива Location

    Location-блоки определяют, как обрабатывать запросы в зависимости от URI.

    Синтаксис и модификаторы

    location [modifier] path {
        # конфигурация
    }
    

    Модификаторы:

    • = — точное совпадение (наивысший приоритет)
    • ^~ — преимущественное префиксное совпадение (останавливает поиск regex)
    • ~ — regex с учетом регистра
    • ~* — regex без учета регистра
    • (без модификатора) — префиксное совпадение

    Порядок приоритета location

    1. Точные совпадения =
    2. Преимущественные префиксы ^~
    3. Регулярные выражения ~ и ~* (в порядке появления в файле)
    4. Префиксные совпадения (самый длинный префикс имеет приоритет)

    Примеры location

    # Точное совпадение - наивысший приоритет
    location = /exact {
        return 200 "Exact match";
    }
    
    # Преимущественный префикс - останавливает regex
    location ^~ /images/ {
        root /var/www;
    }
    
    # Regex с учетом регистра
    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
    }
    
    # Regex без учета регистра
    location ~* \.(jpg|png|gif)$ {
        expires 1y;
    }
    
    # Префиксное совпадение
    location /api/ {
        proxy_pass http://backend;
    }
    
    # Дефолтный обработчик
    location / {
        root /var/www/html;
        index index.html;
    }
    

    Root и Alias директивы

    Root директива

    Директива root устанавливает корневую директорию для запросов. Полный путь формируется путем добавления URI запроса к значению root, включая часть location.

    location /images/ {
        root /var/www;
    }
    

    Для запроса /images/logo.png файл будет искаться по пути: /var/www/images/logo.png.

    Alias директива

    Директива alias заменяет часть URI, указанную в location, на путь из alias. Часть location НЕ добавляется к пути.

    location /images/ {
        alias /var/www/static/;
    }
    

    Для запроса /images/logo.png файл будет искаться по пути: /var/www/static/logo.png.

    Важное различие: С root путь location добавляется к корневому пути, с alias — заменяется.

    Try_files директива

    Директива try_files проверяет существование файлов в указанном порядке и обслуживает первый найденный.

    Синтаксис

    try_files file1 file2 ... uri;
    try_files file1 file2 ... =code;
    

    Примеры использования

    # Базовое использование для SPA приложений
    location / {
        try_files $uri $uri/ /index.html;
    }
    

    Как это работает:

    1. Сначала проверяет, существует ли файл $uri
    2. Если нет, проверяет, является ли $uri/ директорией
    3. Если нет, отдает /index.html
    # С named location для проксирования
    location / {
        try_files $uri $uri/ @backend;
    }
    
    location @backend {
        proxy_pass http://backend-server;
    }
    
    # С кодом ошибки
    location / {
        try_files $uri $uri/ =404;
    }
    

    Важно: При использовании try_files внутри location, если последний параметр — URI, Nginx выполнит внутренний редирект.

    Reverse Proxy конфигурация

    Proxy_pass директива

    Директива proxy_pass перенаправляет запросы на proxied-сервер.

    location /api/ {
        proxy_pass http://127.0.0.1:8000;
    }
    

    Важные proxy заголовки

    location / {
        proxy_pass http://backend;
        
        # Передает оригинальный Host header
        proxy_set_header Host $host;
        
        # IP адрес клиента
        proxy_set_header X-Real-IP $remote_addr;
        
        # Цепочка proxy серверов
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # Протокол (http или https)
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    

    Почему это важно: По умолчанию Nginx перезаписывает Host header на адрес proxied-сервера. Приложению на backend нужен оригинальный Host header, чтобы правильно обрабатывать запросы.

    Proxy_redirect

    # Перезаписывает Location и Refresh заголовки в ответах от proxied-сервера
    proxy_redirect http://localhost:8000/ http://example.com/;
    
    # Или для всех URL
    proxy_redirect http://localhost:8000/ /;
    

    Отключение буферизации

    location /stream/ {
        proxy_pass http://backend;
        proxy_buffering off;  # для приложений с низкой задержкой
    }
    

    Upstream и балансировка нагрузки

    Директива upstream определяет группу серверов для балансировки нагрузки.

    Базовая конфигурация

    upstream backend {
        server backend1.example.com:8080;
        server backend2.example.com:8080;
        server backend3.example.com:8080;
    }
    
    server {
        location / {
            proxy_pass http://backend;
        }
    }
    

    Методы балансировки

    Round Robin (по умолчанию) — запросы распределяются последовательно.

    upstream backend {
        server backend1.example.com;
        server backend2.example.com;
    }
    

    Least Connections — запросы направляются на сервер с наименьшим количеством активных соединений.

    upstream backend {
        least_conn;
        server backend1.example.com;
        server backend2.example.com;
    }
    

    IP Hash — клиент всегда направляется на один и тот же сервер на основе IP.

    upstream backend {
        ip_hash;
        server backend1.example.com;
        server backend2.example.com;
    }
    

    Weight — распределение нагрузки с учетом весов.

    upstream backend {
        server backend1.example.com weight=3;
        server backend2.example.com weight=2;
        server backend3.example.com weight=1;
    }
    

    Параметры серверов в upstream

    upstream backend {
        server backend1.example.com:8080 weight=5;
        server backend2.example.com:8080 max_fails=3 fail_timeout=30s;
        server backend3.example.com:8080 max_conns=100;
        server backend4.example.com:8080 backup;  # используется только если основные недоступны
        server backend5.example.com:8080 down;    # временно исключен
    }
    

    SSL/TLS конфигурация

    Базовая HTTPS конфигурация

    server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name example.com;
        
        # Пути к сертификатам
        ssl_certificate /etc/nginx/certs/ssl_certificate.crt;
        ssl_certificate_key /etc/nginx/certs/ssl_certificate.key;
        
        # Протоколы SSL
        ssl_protocols TLSv1.2 TLSv1.3;
        
        # Шифры
        ssl_ciphers HIGH:!aNULL:!MD5;
        
        location / {
            root /var/www/html;
        }
    }
    

    Редирект HTTP на HTTPS

    # Редирект всех HTTP запросов на HTTPS
    server {
        listen 80;
        listen [::]:80;
        server_name example.com www.example.com;
        return 301 https://$host$request_uri;
    }
    
    # HTTPS сервер
    server {
        listen 443 ssl http2;
        server_name example.com www.example.com;
        
        ssl_certificate /path/to/cert.crt;
        ssl_certificate_key /path/to/cert.key;
        
        location / {
            root /var/www/html;
        }
    }
    

    Каноническая форма URL

    # Редирект с example.com на www.example.com
    server {
        listen 443 ssl http2;
        server_name example.com;
        
        ssl_certificate /path/to/cert.crt;
        ssl_certificate_key /path/to/cert.key;
        
        return 301 https://www.example.com$request_uri;
    }
    
    # Основной сервер
    server {
        listen 443 ssl http2;
        server_name www.example.com;
        
        ssl_certificate /path/to/cert.crt;
        ssl_certificate_key /path/to/cert.key;
        
        location / {
            root /var/www/html;
        }
    }
    

    Gzip сжатие

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

    Основная конфигурация

    http {
        # Включить gzip
        gzip on;
        
        # Уровень сжатия (1-9)
        # 5 - оптимальный баланс между размером и CPU
        gzip_comp_level 5;
        
        # Минимальная длина ответа для сжатия
        gzip_min_length 256;
        
        # Включить сжатие для proxied запросов
        gzip_proxied any;
        
        # MIME типы для сжатия
        gzip_types
            text/plain
            text/css
            text/xml
            text/javascript
            application/json
            application/javascript
            application/xml
            application/xml+rss
            application/xhtml+xml
            image/svg+xml;
        
        # Добавить Vary: Accept-Encoding header
        gzip_vary on;
    }
    

    Как это работает: Когда браузер делает запрос, сервер сжимает файл с помощью gzip перед отправкой. Браузер распаковывает его перед отображением пользователю. Уровень сжатия 5 обеспечивает около 75% уменьшения размера для большинства текстовых файлов.

    Примечание: text/html всегда включен в gzip по умолчанию, его не нужно указывать явно.

    Security Headers

    Security headers защищают от различных атак (XSS, clickjacking, MIME-sniffing).

    Основные заголовки безопасности

    # В http или server контексте
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none';" always;
    

    Описание заголовков:

    • Strict-Transport-Security (HSTS) — принудительное использование HTTPS
    • X-Frame-Options — защита от clickjacking, запрещает встраивание в iframe
    • X-Content-Type-Options — запрещает MIME-sniffing браузерами
    • X-XSS-Protection — включает встроенный XSS фильтр браузера
    • Content-Security-Policy — контролирует, какие ресурсы можно загружать
    • Referrer-Policy — контролирует передачу Referer header

    Параметр always: Гарантирует, что Nginx добавит заголовки даже для ответов с кодами 4xx и 5xx.

    Создание переиспользуемого snippet

    # /etc/nginx/snippets/security-headers.conf
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    

    Использование в server-блоке:

    server {
        listen 443 ssl http2;
        server_name example.com;
        
        include snippets/security-headers.conf;
        
        location / {
            root /var/www/html;
        }
    }
    

    Таймауты

    Таймауты контролируют, как долго Nginx ждет различных событий.

    Основные директивы таймаутов

    http {
        # Таймаут для чтения заголовков запроса от клиента
        client_header_timeout 60s;
        
        # Таймаут для чтения тела запроса от клиента
        client_body_timeout 60s;
        
        # Время, в течение которого keepalive соединение остается открытым
        keepalive_timeout 75s;
        
        # Таймаут для отправки ответа клиенту
        send_timeout 60s;
    }
    

    Client_body_timeout

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

    # Для сайтов с загрузкой больших файлов
    client_body_timeout 300s;
    

    Keepalive_timeout

    Определяет, как долго keepalive соединение остается открытым после отправки ответа.

    keepalive_timeout 65s;
    

    Как это работает: После отправки ответа клиенту соединение не закрывается сразу, а остается открытым указанное время. Это позволяет повторно использовать соединение для последующих запросов, избегая накладных расходов на установку нового TCP соединения.

    Оптимизация: Высокое значение позволяет повторно использовать соединения, но потребляет больше ресурсов сервера. Для сайтов с высокой нагрузкой можно уменьшить до 30s.

    Пример для API с большими загрузками

    location /api/upload {
        client_header_timeout 120s;
        client_body_timeout 300s;
        client_max_body_size 100m;
        
        proxy_pass http://backend;
        proxy_read_timeout 300s;
        proxy_connect_timeout 60s;
    }
    

    Размеры буферов

    Client_body_buffer_size

    Определяет размер буфера для чтения тела запроса клиента.

    # По умолчанию 8k или 16k
    client_body_buffer_size 16k;
    

    Как это работает: Если размер тела запроса превышает размер буфера, Nginx записывает его во временный файл на диске. Это замедляет обработку из-за дополнительных операций I/O.

    Оптимизация: Для API с большими загрузками можно увеличить размер буфера, но не устанавливайте его слишком большим, чтобы избежать уязвимостей к DoS атакам.

    Client_max_body_size

    Ограничивает максимальный размер тела запроса клиента.

    http {
        client_max_body_size 10M;
    }
    
    # Или для конкретного location
    location /upload {
        client_max_body_size 100M;
    }
    

    По умолчанию: 1M. Если клиент отправляет файл больше этого размера, Nginx возвращает ошибку 413 (Request Entity Too Large).

    PHP-FPM конфигурация

    Базовая настройка для PHP

    server {
        listen 80;
        server_name example.com;
        root /var/www/html;
        index index.php index.html;
        
        location / {
            try_files $uri $uri/ =404;
        }
        
        # Обработка PHP файлов
        location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            
            # Для UNIX сокета
            fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
            
            # Или для TCP/IP
            # fastcgi_pass 127.0.0.1:9000;
            
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
        
        # Запрет доступа к .htaccess файлам
        location ~ /\.ht {
            deny all;
        }
    }
    

    Как это работает: Когда запрашивается файл .php, Nginx передает запрос PHP-FPM через FastCGI протокол. PHP-FPM выполняет скрипт и возвращает результат обратно Nginx.

    Разные версии PHP для разных сайтов

    server {
        server_name site1.com;
        location ~ \.php$ {
            fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
            include fastcgi_params;
        }
    }
    
    server {
        server_name site2.com;
        location ~ \.php$ {
            fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
            include fastcgi_params;
        }
    }
    

    Rewrite и редиректы

    Return директива

    Директива return — самый эффективный способ создания редиректов.

    # Постоянный редирект (301)
    location /old-page {
        return 301 /new-page;
    }
    
    # Временный редирект (302)
    location /temp {
        return 302 /temporary-location;
    }
    
    # Редирект на другой домен
    server {
        server_name old-domain.com;
        return 301 https://new-domain.com$request_uri;
    }
    

    Почему return лучше rewrite: return немедленно останавливает обработку и отправляет редирект, без дополнительных проверок. Это быстрее и проще в понимании.

    Rewrite директива

    Директива rewrite использует регулярные выражения для изменения URI.

    Синтаксис:

    rewrite regex replacement [flag];
    

    Флаги:

    • last — останавливает обработку rewrite и начинает новый поиск location
    • break — останавливает обработку rewrite в текущем контексте
    • redirect — временный редирект (302)
    • permanent — постоянный редирект (301)

    Примеры rewrite

    # Простой rewrite с захватом групп
    location /data/ {
        rewrite ^(/data/.*)/geek/(\w+)\.?.*$ $1/linux/$2.html last;
    }
    # /data/distro/geek/test.php -> /data/distro/linux/test.html
    

    Как это работает: $1 и $2 захватывают части URI, заключенные в скобки () в регулярном выражении.

    # Rewrite для контроллера
    location /linux/ {
        rewrite ^/linux/(.*)$ /linux.php?distro=$1 last;
    }
    # /linux/ubuntu -> /linux.php?distro=ubuntu
    
    # Условный rewrite
    if ($scheme = "http") {
        rewrite ^ https://example.com$uri permanent;
    }
    

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

    location /old-path/ {
        return 301 /new-path$request_uri;
    }
    

    Переменные Nginx

    Nginx предоставляет множество встроенных переменных для использования в конфигурации.

    Основные переменные запроса

    # IP адрес клиента
    $remote_addr
    
    # Порт клиента
    $remote_port
    
    # Полный URI запроса с параметрами
    $request_uri
    
    # Нормализованный URI (после rewrite)
    $uri
    
    # HTTP метод (GET, POST, etc.)
    $request_method
    
    # Протокол запроса (http или https)
    $scheme
    
    # Host header из запроса
    $host
    
    # Доменное имя сервера
    $server_name
    
    # Порт сервера
    $server_port
    
    # Query string (параметры после ?)
    $args или $query_string
    

    Пример использования переменных

    # Логирование с переменными
    log_format custom '$remote_addr - $remote_user [$time_local] '
                      '"$request" $status $body_bytes_sent '
                      '"$http_referer" "$http_user_agent"';
    
    # Установка заголовков
    location / {
        proxy_pass http://backend;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    
    # Условная логика
    if ($request_method = POST) {
        return 405;
    }
    

    Переменные для доступа к параметрам

    # Доступ к конкретному query параметру
    $arg_name  # для параметра ?name=value
    
    # Пример
    if ($arg_debug = "true") {
        return 200 "Debug mode";
    }
    

    Map директива

    Директива map создает новые переменные на основе значений других переменных.

    Синтаксис:

    map $source_variable $destination_variable {
        default default_value;
        value1 result1;
        value2 result2;
        ~regex result3;
    }
    

    Примеры использования map

    # Редирект поддоменов
    map $host $redirect_host {
        default "example.org";
        ~^(?<subdomain>\S+)\.example\.com$ $subdomain.example.org;
    }
    
    server {
        server_name .example.com;
        return 301 https://$redirect_host$request_uri;
    }
    
    # Определение backend на основе User-Agent
    map $http_user_agent $backend {
        default backend_default;
        ~*bot backend_bots;
        ~*mobile backend_mobile;
    }
    
    server {
        location / {
            proxy_pass http://$backend;
        }
    }
    
    # Установка переменной для блокировки IP
    map $remote_addr $blocked {
        default 0;
        192.168.1.10 1;
        10.0.0.5 1;
    }
    
    server {
        if ($blocked) {
            return 403;
        }
    }
    

    Важно: map директива может использоваться только в http контексте, а не внутри server или location.

    Rate Limiting (ограничение частоты запросов)

    Rate limiting защищает сервер от перегрузки и DoS атак.

    Limit_req (ограничение запросов)

    http {
        # Определяем зону для отслеживания
        limit_req_zone $binary_remote_addr zone=limitbyip:10m rate=10r/s;
        
        # Статус для отклоненных запросов
        limit_req_status 429;
        
        server {
            location /api/ {
                # Применяем ограничение
                limit_req zone=limitbyip burst=20 nodelay;
                
                proxy_pass http://backend;
            }
        }
    }
    

    Параметры:

    • $binary_remote_addr — ключ для идентификации клиентов (IP адрес)
    • zone=limitbyip:10m — имя зоны и размер памяти (10 МБ)
    • rate=10r/s — максимальная частота (10 запросов в секунду)
    • burst=20 — разрешает временные всплески до 20 запросов
    • nodelay — обрабатывает burst запросы немедленно, без задержки

    Как это работает: Nginx отслеживает количество запросов от каждого IP адреса. Если частота превышает указанный лимит, запросы задерживаются (помещаются в очередь) или отклоняются с ошибкой 429.

    Limit_conn (ограничение соединений)

    http {
        # Определяем зону
        limit_conn_zone $binary_remote_addr zone=connlimit:10m;
        
        server {
            location /downloads/ {
                # Максимум 5 одновременных соединений от одного IP
                limit_conn connlimit 5;
                
                root /var/www/files;
            }
        }
    }
    

    Множественные ограничения

    http {
        limit_req_zone $binary_remote_addr zone=perip:10m rate=1r/s;
        limit_req_zone $server_name zone=perserver:10m rate=10r/s;
        
        server {
            location / {
                limit_req zone=perip burst=5 nodelay;
                limit_req zone=perserver burst=10;
                
                proxy_pass http://backend;
            }
        }
    }
    

    MIME типы

    Default_type и mime.types

    Nginx использует MIME типы для определения типа содержимого файлов.

    http {
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
    }
    

    Как это работает: Файл mime.types содержит сопоставление расширений файлов и MIME типов. Если расширение файла не найдено в mime.types, используется default_type.

    application/octet-stream означает, что браузер будет скачивать файл как бинарный, вместо попытки отобразить его.

    Добавление пользовательских MIME типов

    http {
        include mime.types;
        
        types {
            application/javascript mjs;
            application/wasm wasm;
        }
    }
    

    Логирование

    Access log

    http {
        # Определение формата лога
        log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for"';
        
        # Использование формата
        access_log /var/log/nginx/access.log main;
    }
    

    Пользовательский формат лога

    http {
        log_format custom '$remote_addr - $remote_user [$time_local] '
                          '"$request" $status $body_bytes_sent '
                          '"$http_referer" "$http_user_agent" '
                          '$request_time $upstream_response_time';
        
        access_log /var/log/nginx/custom.log custom;
    }
    

    Полезные переменные для логов:

    • $request_time — полное время обработки запроса
    • $upstream_response_time — время ответа backend сервера
    • $status — код HTTP ответа
    • $body_bytes_sent — количество отправленных байт

    Error log

    # Уровни: debug, info, notice, warn, error, crit, alert, emerg
    error_log /var/log/nginx/error.log warn;
    

    Отключение логов для статики

    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        access_log off;
        expires 1y;
    }
    

    Include директива

    Директива include позволяет подключать внешние конфигурационные файлы.

    http {
        include /etc/nginx/mime.types;
        include /etc/nginx/conf.d/*.conf;
        
        server {
            include /etc/nginx/snippets/ssl-params.conf;
            include /etc/nginx/snippets/security-headers.conf;
        }
    }
    

    Преимущества использования include:

    • Модульность конфигурации
    • Переиспользование общих настроек
    • Упрощение управления большими конфигурациями

    Структура директорий

    /etc/nginx/
    ├── nginx.conf                    # Главный файл
    ├── conf.d/                       # Дополнительные конфигурации
    │   └── default.conf
    ├── sites-available/              # Доступные сайты
    │   ├── example.com.conf
    │   └── test.com.conf
    ├── sites-enabled/                # Активные сайты (симлинки)
    │   └── example.com.conf -> ../sites-available/example.com.conf
    └── snippets/                     # Переиспользуемые фрагменты
        ├── ssl-params.conf
        └── security-headers.conf
    

    If директива и её подводные камни

    Директива if в Nginx работает не так, как ожидается в большинстве языков программирования.

    Когда безопасно использовать if

    Безопасно только с директивами return и rewrite:

    if ($scheme = "http") {
        return 301 https://$host$request_uri;
    }
    
    if ($request_method = POST) {
        return 405;
    }
    

    Проблемы с if

    # НЕПРАВИЛЬНО - может вызвать segmentation fault
    location / {
        if ($request_uri ~* "admin") {
            proxy_pass http://admin_backend;
        }
        root /var/www/html;
    }
    

    Почему это проблема: if в Nginx оценивается на этапе конфигурации, а не на каждом запросе. Это может приводить к неожиданному поведению и ошибкам.

    Альтернативы if

    Используйте map вместо if:

    map $request_uri $backend {
        default http://default_backend;
        ~*admin http://admin_backend;
    }
    
    server {
        location / {
            proxy_pass $backend;
        }
    }
    

    Используйте разные location блоки:

    location ~* /admin {
        proxy_pass http://admin_backend;
    }
    
    location / {
        proxy_pass http://default_backend;
    }
    

    Events контекст и оптимизация производительности

    Основные директивы events

    events {
        # Количество одновременных соединений на worker
        worker_connections 1024;
        
        # Метод обработки событий (зависит от ОС)
        use epoll;  # Linux
        # use kqueue;  # FreeBSD, macOS
        
        # Принимать несколько соединений за раз
        multi_accept on;
    }
    

    Методы обработки событий

    Linux: epoll — наиболее эффективный метод для Linux систем.

    FreeBSD/macOS: kqueue — оптимизированный для BSD систем.

    Почему это важно: Эти методы используют механизмы ядра операционной системы для эффективной обработки тысяч соединений одновременно без блокировки.

    Полная оптимизация производительности

    user nginx;
    worker_processes auto;
    worker_rlimit_nofile 65535;
    error_log /var/log/nginx/error.log warn;
    pid /var/run/nginx.pid;
    
    events {
        worker_connections 4096;
        use epoll;
        multi_accept on;
    }
    
    http {
        # Основные настройки
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        
        # Таймауты
        keepalive_timeout 65;
        client_header_timeout 60;
        client_body_timeout 60;
        send_timeout 60;
        
        # Буферы
        client_body_buffer_size 16k;
        client_max_body_size 10m;
        
        # Gzip
        gzip on;
        gzip_comp_level 5;
        gzip_min_length 256;
        gzip_types text/plain text/css text/xml text/javascript 
                   application/json application/javascript application/xml;
        
        # Кэширование открытых файлов
        open_file_cache max=10000 inactive=30s;
        open_file_cache_valid 60s;
        open_file_cache_min_uses 2;
        open_file_cache_errors on;
    }
    

    Проверка и перезагрузка конфигурации

    Проверка синтаксиса

    # Проверка конфигурации
    sudo nginx -t
    
    # Вывод:
    # nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    # nginx: configuration file /etc/nginx/nginx.conf test is successful
    

    Управление Nginx

    # Запуск
    sudo systemctl start nginx
    
    # Остановка
    sudo systemctl stop nginx
    
    # Перезапуск
    sudo systemctl restart nginx
    
    # Перезагрузка конфигурации без разрыва соединений
    sudo systemctl reload nginx
    
    # Или напрямую
    sudo nginx -s reload
    
    # Проверка статуса
    sudo systemctl status nginx
    

    Сигналы Nginx

    # Изящная остановка (ждет завершения запросов)
    sudo nginx -s quit
    
    # Быстрая остановка
    sudo nginx -s stop
    
    # Перезагрузка конфигурации
    sudo nginx -s reload
    
    # Переоткрыть лог-файлы
    sudo nginx -s reopen
    

    Полный пример конфигурации production-сервера

    user nginx;
    worker_processes auto;
    worker_rlimit_nofile 65535;
    error_log /var/log/nginx/error.log warn;
    pid /var/run/nginx.pid;
    
    events {
        worker_connections 4096;
        use epoll;
        multi_accept on;
    }
    
    http {
        include /etc/nginx/mime.types;
        default_type application/octet-stream;
        
        # Логирование
        log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for" '
                        '$request_time $upstream_response_time';
        
        access_log /var/log/nginx/access.log main;
        
        # Основные настройки
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        
        # Таймауты
        keepalive_timeout 65;
        client_header_timeout 60;
        client_body_timeout 60;
        send_timeout 60;
        
        # Буферы
        client_body_buffer_size 16k;
        client_max_body_size 10m;
        
        # Gzip сжатие
        gzip on;
        gzip_comp_level 5;
        gzip_min_length 256;
        gzip_proxied any;
        gzip_vary on;
        gzip_types
            text/plain
            text/css
            text/xml
            text/javascript
            application/json
            application/javascript
            application/xml
            application/xml+rss;
        
        # Rate limiting
        limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
        limit_req_status 429;
        
        # Upstream для балансировки
        upstream backend {
            least_conn;
            server backend1.example.com:8080 weight=3;
            server backend2.example.com:8080 weight=2;
            server backend3.example.com:8080 backup;
        }
        
        # Редирект HTTP на HTTPS
        server {
            listen 80;
            listen [::]:80;
            server_name example.com www.example.com;
            return 301 https://www.example.com$request_uri;
        }
        
        # Редирект с example.com на www.example.com
        server {
            listen 443 ssl http2;
            listen [::]:443 ssl http2;
            server_name example.com;
            
            ssl_certificate /etc/nginx/ssl/certificate.crt;
            ssl_certificate_key /etc/nginx/ssl/private.key;
            ssl_protocols TLSv1.2 TLSv1.3;
            ssl_ciphers HIGH:!aNULL:!MD5;
            
            return 301 https://www.example.com$request_uri;
        }
        
        # Основной сервер
        server {
            listen 443 ssl http2;
            listen [::]:443 ssl http2;
            server_name www.example.com;
            
            root /var/www/html;
            index index.php index.html;
            
            # SSL
            ssl_certificate /etc/nginx/ssl/certificate.crt;
            ssl_certificate_key /etc/nginx/ssl/private.key;
            ssl_protocols TLSv1.2 TLSv1.3;
            ssl_ciphers HIGH:!aNULL:!MD5;
            
            # Security headers
            add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
            add_header X-Frame-Options "DENY" always;
            add_header X-Content-Type-Options "nosniff" always;
            add_header X-XSS-Protection "1; mode=block" always;
            add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none';" always;
            
            # Логирование
            access_log /var/log/nginx/example.com.access.log main;
            error_log /var/log/nginx/example.com.error.log warn;
            
            # Основной location
            location / {
                try_files $uri $uri/ /index.php?$args;
            }
            
            # API с rate limiting
            location /api/ {
                limit_req zone=general burst=20 nodelay;
                
                proxy_pass http://backend;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                
                proxy_read_timeout 300s;
                proxy_connect_timeout 60s;
            }
            
            # PHP обработка
            location ~ \.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
            }
            
            # Статические файлы
            location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
                expires 1y;
                add_header Cache-Control "public, immutable";
                access_log off;
            }
            
            # Запрет доступа к скрытым файлам
            location ~ /\. {
                deny all;
                access_log off;
                log_not_found off;
            }
            
            # Health check endpoint
            location = /health {
                access_log off;
                return 200 "OK\n";
                add_header Content-Type text/plain;
            }
        }
    }
    

    Заключение

    Эта статья охватывает основные и продвинутые аспекты конфигурации Nginx:

    • Архитектура — master и worker процессы, обработка соединений
    • Структура конфигурации — иерархия контекстов и директив
    • Location блоки — модификаторы и приоритеты обработки
    • Reverse proxy — проксирование запросов на backend серверы
    • Балансировка нагрузки — upstream блоки и методы распределения
    • SSL/TLS — настройка HTTPS и редиректы
    • Оптимизация — gzip, кэширование, буферы и таймауты
    • Безопасность — security headers и rate limiting
    • PHP-FPM — интеграция с PHP приложениями
    • Логирование — форматы логов и пользовательские настройки

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


    0 0 2 Ответить
  • kirilljsxK
    kirilljsx
    Пресс-секретарь белого дома в 28 лет

    a77b05fb429d71abb6ce1b29934c39b76dee4d96.jpeg

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

    Цитирую:

    Это красивое лицо, эти губы, которые не останавливаются. “Ап, ап, ап, ап”, как маленький пулемет, она ничего не боится. Знаете, почему? Потому что у нас правильная политика, — сказал американский президент.

    Я вообще по своей натуре люблю гуглить и задавать вопросы на различные тематики. И решил узнать про Кэролайн.


    И так вот что мне удалось разузнать:

    Кэролайн Ливитт родилась 24 августа 1997 года в Аткинсоне, штат Нью-Гэмпшир, США.

    Училась в Центральной католической средней школе в Лоуренсе, штат Массачусетс, а затем в Колледже Святого Ансельма, где получила степень бакалавра по коммуникациям и политологии в 2019 году. Во время учёбы проходила стажировки на Fox News и в Управлении президентской корреспонденции Белого дома, а также работала на WMUR-TV.​

    После окончания колледжа в 2019 году устроилась в Белый дом при первой администрации Трампа: сначала писала письма от имени президента, затем стала помощником пресс-секретаря Кейли Макинэни. В 2021–2022 годах работала директором по коммуникациям конгрессвумен Элиз Стефаник, баллотировалась в Палату представителей от Нью-Гэмпшира (выиграла праймериз, но проиграла выборы), затем представляла MAGA Inc. и с января 2024 года — национальный пресс-секретарь кампании Трампа 2024 года.​

    15 ноября 2024 года президент-элект Трамп назначил её пресс-секретарём Белого дома; она вступила в должность 20 января 2025 года в возрасте 27 лет (самая молодая в истории США), благодаря опыту в коммуникациях Трампа и лояльности. Первый брифинг провела 28 января 2025 года.


    С виду я сначала подумал - хм, женщине на вид лет 35+ или того больше. А оказалось она совсем еще молода и в свои 28 уже в белом доме.
    Как по мне это круто 👍


    2 0 1 Ответить
  • kirilljsxK
    kirilljsx
    Github как перенести репозиторий в организацию или передать пользователю

    Небольшой гайд как перенести/передать свой репозиторий в организацию или другому пользователю.

    И так для начала перейдем в нужный нам репозиторий, и далее переходим в настройки (Settings):
    ee048ab3-2eb2-4322-8cf4-bf8ab7b10f90-image.png

    После того как мы попали на страницу настроек репозитория, нам необходимо промотать страницу вниз, где мы уже найдем Danger Master Zone:

    429447c1-8fa5-47d7-b918-47a28ed330a1-image.png

    И тут нам необходимо выбрать жмякнуть на Transfer.

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

    eaeb2de9-7e97-4390-893b-61ea896159b1-image.png


    0 0 2 Ответить
  • AladdinA
    Aladdin
    Релиз децентрализованной видеовещательной платформы PeerTube 8.0

    PeerTube — это свободная децентрализованная видеоплатформа, построенная на протоколе ActivityPub и использующая федеративную модель: множество независимых серверов (инстансов) объединяются в единую сеть. Проект развивается некоммерческой организацией Framasoft и позиционируется как альтернатива крупным централизованным площадкам, позволяющая сообществам и организациям контролировать свои данные и правила модерации.

    Официальный сайт проекта — joinpeertube.org, где доступны обзор возможностей, FAQ и ссылки на мобильные клиенты. Техническая документация и руководство по установке размещены на docs.joinpeertube.org, а исходный код и раздел релизов — в репозитории Chocobozzz/PeerTube на GitHub. Дополнительные справочные материалы о проекте можно найти в статье русской Википедии (ru.wikipedia.org/wiki/PeerTube) и на портале Fediverse.Party.

    Федерация и P2P‑архитектура

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

    При просмотре видео платформа по возможности раздает медиаконтент по P2P‑схеме: браузеры зрителей соединяются между собой по WebRTC, а сегменты потока доставляются по протоколу HLS (HTTP Live Streaming). Если для конкретного ролика нет активных зрителей, данные отдаются напрямую с исходного сервера по схеме WebSeed, так что видео остается доступным даже без P2P‑клиентов.

    PeerTube позволяет серверам кэшировать и хранить копии чужих видео, формируя распределенную сеть не только из пользователей, но и из серверов, что повышает отказоустойчивость и снижает нагрузку на исходный инстанс. Кроме уже загруженных файлов, платформа поддерживает потоковое вещание (live streaming) с доставкой в P2P‑режиме, для которого можно использовать стандартные программы вроде OBS Studio.

    Публикация и потребление контента

    Чтобы начать вещание через PeerTube, достаточно зарегистрироваться на одном из публичных инстансов, загрузить видеоролик, добавить описание и набор тегов. После модерации (если она включена) видео становится доступным не только пользователям выбранного сервера, но и всей федеративной сети PeerTube благодаря механизму федерации и подписок.

    Видео можно встраивать на сторонние сайты через специальный web‑виджет с плеером, по аналогии с тем, как обычно встраиваются ролики с YouTube. Отслеживать новые публикации удобно через подписку на каналы из федеративных соцсетей (например, совместимых с ActivityPub платформ, упомянутых на joinpeertube.org), а также через RSS‑ленты, которые PeerTube автоматически формирует для каналов.

    Существуют отдельные проекты и сервисы, которые агрегируют или облегчают работу с сетью PeerTube, например каталог инстансов и контента на joinpeertube.org и отдельные публичные хостинги вроде PeerTube.TV. Для массовой загрузки и интеграции с другими системами доступны сторонние инструменты, такие как Node.js‑скрипт peertube-mass-uploader.

    Инфраструктура сети и развёртывание

    По данным публикации на opennet.ru, в федеративную сеть PeerTube на момент выхода новости входило 1289 серверов, поддерживаемых сообществами, организациями и отдельными энтузиастами. На официальном сайте проекта также приводятся агрегированные оценки масштаба сети: сотни тысяч пользователей, свыше миллиона видео и более полутора тысяч инстансов.

    Если пользователю не подходят правила или политика конкретного инстанса, он может свободно перейти на другой сервер или развернуть собственную инсталляцию. Для быстрого старта разработчики предоставляют готовые Docker‑образы, в частности официальный образ chocobozzz/peertube и вспомогательный web‑сервер chocobozzz/peertube-webserver. Подробное руководство по установке в Docker опубликовано в разделе документации «Docker guide».

    Веб‑интерфейс PeerTube реализован на базе фреймворка Angular, а серверная часть использует современный стек веб‑технологий и интеграцию с протоколом ActivityPub. Технические детали, схемы архитектуры и инструкции по вкладу в разработку доступны в разделе «Contribute» официальной документации и в README репозитория Chocobozzz/PeerTube.

    Новое в PeerTube 8.0

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

    Это делает внешний вид инстанса более настраиваемым и позволяет авторам подстраивать оформление под бренд или тематику конкретного канала.
    5df83f28-e28e-4664-9de1-94c51f2aba80-image.png

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

    c47cd345-12c0-4681-9ba5-2800bac2b2ed-image.png
    dd0afd74-c3e8-45f6-b66e-e0836c11a365-image.png

    Изменён внешний вид уведомлений, которые теперь лучше сочетаются с темой оформления PeerTube
    710b8380-2d58-4a6d-8b63-ae7933b872c0-image.png

    Подробный перечень изменений и технические примечания к обновлению можно посмотреть в новости «PeerTube v8: manage your videos with your team» и на странице релизов GitHub.

    Настоящий материал подготовлен по мотивам официальных анонсов проекта PeerTube и заметки на opennet.ru, с уважением к авторским правам.


    1 0 2 Ответить
  • kirilljsxK
    kirilljsx
    Call of Duty и Fallout официально могут вернуться на российский рынок в 2026 году — СМИ

    В марте 2022 года компания Activision Blizzard полностью покинула российский рынок, запретив оплачивать свои игры: World of Warcraft, Call of Duty, Diablo и другие.

    В 2023 году Activision Blizzard были выкуплены Microsoft, которые также являются владельцами Bethesda. Они же запретили покупать свои игры в Steam в российском регионе в 2022 году.

    Недавно компании зарегистрировали 7 товарных знаков в РФ: Dishonored, Starfield, Wolfenstein, Deathloop, Fallout, Quake, а также Call of Duty. Под брендами Call of Duty и Fallout они смогут выпускать на российском рынке программное обеспечение для игр.

    fa651508-841c-4ee8-be2b-c91cfadc81b9-image.png

    88ca333c-4b8d-4386-8f66-a8d97c263a20-image.png


    4 0 1 Ответить
  • MugiwaraM
    Mugiwara
    Unix‑шпаргалка для веб‑разработчика: команды для кибербезопасности и поддержки сайтов

    Материал ориентирован на разработчиков и тех, кто не живёт в терминале 24/7, но хочет уверенно чувствовать себя на сервере.


    1. Навигация и файловая структура

    Где я и что вокруг

    pwd          # показать текущую директорию
    ls           # список файлов
    ls -la       # список с правами/владельцами/скрытыми файлами
    cd /path     # перейти в директорию
    cd ..        # на уровень выше
    cd -         # переключиться в предыдущую директорию
    

    Для чего:

    • Проверка, куда деплоится сайт, где лежат логи и конфиги.
    • Контроль, нет ли «левых» директорий рядом с проектом.

    2. Просмотр и поиск по файлам (логи, конфиги, шеллы)

    Быстрый просмотр файлов

    cat file.log                  # вывести весь файл
    less file.log                 # пролистывать вверх/вниз, поиск по /строка
    head -n 50 file.log           # первые 50 строк
    tail -n 50 file.log           # последние 50 строк
    tail -f file.log              # «онлайн» просмотр лога
    

    Поиск по тексту (ошибки, IP, подозрительные строки)

    grep "ERROR" file.log                 # найти строки с ERROR
    grep -i "warning" file.log            # регистронезависимый поиск
    grep -R "eval(base64_decode" .       # рекурсивный поиск по проекту
    grep -R "site.ru" /var/log           # где в логах фигурирует ваш домен
    

    Комбо для live‑мониторинга ошибок:

    tail -f /var/log/nginx/error.log | grep --line-buffered "site.ru"
    

    3. Права, владельцы, подозрительные файлы

    Права и владельцы

    ls -la                      # посмотреть права и владельца
    chown user:group file       # сменить владельца
    chmod 644 file              # стандартные права для файлов
    chmod 755 dir               # стандартные права для директорий
    

    Типичные значения для PHP-проектов (без shared‑hosting‑извращений):

    • директории: 755
    • файлы: 644
    • никакого 777 в проде, если не хотите неожиданных «сюрпризов».

    Поиск подозрительных файлов

    find . -name "*.php" -mtime -1             # новые/изменённые за сутки
    find . -name "*.php" -size +1M             # слишком «толстые» php-файлы
    find . -perm -o+w -type f                  # файлы, доступные на запись всем
    find . -name ".*" -type f                  # скрытые файлы (в т.ч. пассажи типа .something.php)
    

    4. Сеть: кто подключён, какие порты, что слушает

    Открытые порты и процессы

    ss -tulpen         # список слушающих портов и процессов
    ss -tnp            # активные TCP‑подключения
    

    Кто подключён по SSH

    who                # кто в системе
    w                  # кто, откуда и что запускает
    last -n 20         # последние 20 входов в систему
    

    Запросы к сайту в реальном времени (Nginx)

    tail -f /var/log/nginx/access.log
    tail -f /var/log/nginx/access.log | grep "POST"
    tail -f /var/log/nginx/access.log | grep "wp-login"    # поиск брут‑запросов к WP, как пример
    

    5. Процессы, память, нагрузка

    Общая картина

    top              # живой монитор ресурсов
    htop             # более удобная версия (если установлена)
    uptime           # load average и время работы сервера
    free -h          # память
    df -h            # диски и свободное место
    du -sh *         # размер директорий в текущем каталоге
    

    Кто ест CPU / RAM

    ps aux --sort=-%cpu | head      # топ по CPU
    ps aux --sort=-%mem | head      # топ по памяти
    

    Убить зависший процесс

    ps aux | grep php-fpm           # найти PID
    kill 12345                      # мягко завершить
    kill -9 12345                   # жёстко (в крайнем случае)
    

    6. Работа с архивами и бэкапами

    Создать архив проекта

    tar czf backup-`date +%F`.tar.gz /var/www/site
    

    Распаковать архив

    tar xzf backup-2025-12-08.tar.gz -C /var/www/site
    

    Полезно для:

    • быстрых ручных бэкапов перед обновлениями;
    • переносов проекта между серверами.

    7. SSH и ключи: безопасность доступа к серверу

    Создать SSH‑ключ

    ssh-keygen -t ed25519 -C "your_email@example.com"
    

    Скопировать ключ на сервер

    ssh-copy-id user@server
    

    Подключиться с ключом

    ssh user@server
    

    Базовые практики:

    • выключить парольный вход в sshd_config (PasswordAuthentication no);
    • использовать нестандартный порт, если это допустимо инфраструктурно;
    • ограничивать вход по IP, если есть возможность.

    8. Мониторинг и анализ логов (Nginx, Apache, SSH)

    Типичные места логов

    /var/log/nginx/access.log
    /var/log/nginx/error.log
    /var/log/apache2/access.log
    /var/log/apache2/error.log
    /var/log/auth.log        # попытки входа в систему (SSH и не только)
    

    Примеры команд

    # 20 последних ошибок Nginx
    tail -n 20 /var/log/nginx/error.log
    
    # Подозрительные многократные попытки логина по SSH
    grep "Failed password" /var/log/auth.log | tail
    
    # Часто встречающиеся IP (грубый подсчёт)
    awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
    

    9. Базовое взаимодействие с Git (деплой, код, аудит изменений)

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

    git status                     # что изменилось
    git diff                       # посмотреть реальные изменения
    git log --oneline --decorate   # история коммитов
    git blame file.php             # кто и когда менял строки в файле
    

    Подозрительные изменения можно быстрой командой просканировать и найти:

    grep -R "base64_decode" .git
    grep -R "system(" .
    

    10. Быстрые однострочники для безопасника и админа

    Несколько «готовых» рецептов, которые можно брать и вставлять.

    Показать топ‑10 IP по числу запросов к сайту:

    awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
    

    Поиск всех PHP‑файлов, изменённых за последние 2 часа:

    find /var/www/site -name "*.php" -mmin -120 -print
    

    Поиск сигнатур типичных веб-шеллов:

    grep -R "eval(base64_decode" /var/www/site
    grep -R "passthru(" /var/www/site
    grep -R "shell_exec(" /var/www/site
    

    11. Минимальный набор для «я не админ, но надо срочно глянуть»

    Если совсем нет времени, а доступ к SSH есть, то хотя бы эти команды стоит держать под рукой:

    # 1. Диски, память, нагрузка
    df -h
    free -h
    uptime
    
    # 2. Логи сайта
    tail -n 50 /var/log/nginx/error.log
    tail -n 50 /var/log/nginx/access.log
    
    # 3. Кто подключён по SSH
    w
    last -n 10
    
    # 4. Изменённые PHP-файлы за сутки в проекте
    cd /var/www/site
    find . -name "*.php" -mtime -1 -print
    

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

    • веб‑разработчика, которого внезапно сделали «ответственным за сервер»;
    • кибербезопасности на уровне проверки логов, прав и подозрительных файлов;
    • быстрой диагностики типичных проблем с сайтом.

    4 0 3 Ответить
  • kirilljsxK
    kirilljsx
    RTX 3060 12GB для глубокого обучения: стоит ли использовать

    3060.jpg

    RTX 3060 с 12 ГБ VRAM остается актуальной бюджетной видеокартой для обучения и прототипирования моделей глубокого обучения, особенно для студентов и небольших проектов. Ее ключевым преимуществом служит большой объем памяти, который позволяет загружать модели и батчи данных, не сталкиваясь с ошибками нехватки VRAM на 6–8 ГБ картах.

    Однако для крупных LLM или производственных задач она уступает по скорости новым поколениям GPU.


    Преимущества и недостатки

    • 12 ГБ GDDR6 VRAM: Подходит для CNN, небольших трансформеров (до 1B параметров) и генерации изображений вроде Stable Diffusion на стандартных разрешениях.​

    • Доступная цена: Хороший вариант для входного уровня, особенно на вторичном рынке, с поддержкой CUDA в PyTorch и TensorFlow.​

    • Универсальность: Работает для игр и ML, что оправдывает покупку.

    • Медленный вычислительный блок: Ampere-архитектура уступает Ada Lovelace по производительности в больших сетях.​

    • Ограничения по масштабу: Неудобно для multi-GPU или обучения моделей 7B+ без облака.​

    Что реально запустить

    Карта справляется с обучением малых CNN, GNN и тонкой настройкой трансформеров при использовании mixed precision и gradient checkpointing. Для Stable Diffusion или инференса квантизованных LLM возможны небольшие батчи, но время на эпоху будет дольше, чем на RTX 40-й серии. В 2025 году она подходит для локального прототипирования с последующим переносом в облако вроде RunPod.​​

    Советы по оптимизации

    Применяйте AMP для снижения потребления памяти, квантизацию для инференса и накопление градиентов вместо больших батчей. Комбинируйте с облачными GPU для тяжелых задач - это экономит ресурсы. Альтернативы: б/у RTX 3090 (24 ГБ) для большего VRAM или аренда A100/H100


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


    3 0 2 Ответить
  • AladdinA
    Aladdin
    Критическая уязвимость в React.js Next.js (CVE-2025-55182, CVE-2025-66478): Как защитить свой сайт

    ac1694f6-c49a-4cdd-89f3-a9538f609828-image.png

    Что произошло?

    3 декабря 2025 года была раскрыта критическая уязвимость безопасности в React Server Components, которая имеет максимальный рейтинг CVSS 10.0. Уязвимость позволяет неаутентифицированному злоумышленнику выполнить произвольный код на сервере вашего приложения.

    Это означает: если вы используете Next.js версии 15.x или выше с поддержкой React Server Components — ваше приложение потенциально под угрозой.

    Суть уязвимости

    Уязвимость CVE-2025-55182 находится в том, как React декодирует полезные нагрузки, отправляемые на эндпоинты React Server Function. Злоумышленник может создать вредоносный HTTP-запрос к любому Server Function эндпоинту, который при десериализации приведёт к удалённому выполнению кода (RCE) на сервере.

    Важный момент: даже если ваше приложение не реализует явно эндпоинты React Server Function, оно всё равно может быть уязвимым, если использует React Server Components. Это касается фреймворков, которые поддерживают этот функционал.

    Затронутые версии

    Уязвимость присутствует в следующих версиях пакетов React:

    • React 19.0
    • React 19.1.0
    • React 19.1.1
    • React 19.2.0

    Аналогичная уязвимость (CVE-2025-66478) была выявлена в реализации протокола RSC (React Server Components) во фреймворке Next.js. Проблема затрагивает приложения, использующие App Router и ветки Next.js 15.x и 16.x. Утверждается, что уязвимость проявляется в конфигурации Next.js по умолчанию (стандартное приложение, создаваемое утилитой create‑next‑app, подвержено атаке).

    В Next.js это затрагивает версии 15.0.x и выше, а также canary-релизы 14.3.0-canary.77 и позже.

    Затронутые фреймворки и инструменты

    Помимо Next.js, уязвимость влияет на:

    • React Router
    • Waku
    • @parcel/rsc
    • @vitejs/plugin-rsc
    • rwsdk (Redwood SDK)

    Как защитить свое приложение?

    1. Немедленно обновитесь

    Исправление было выпущено в:

    • React 19.0.1, 19.1.2 и 19.2.1
    • Next.js с соответствующими патчами для каждой версии

    2. Инструкции по обновлению Next.js

    Выберите команду обновления в зависимости от вашей текущей версии:

    # Для Next.js 15.0.x
    npm install next@15.0.5
    
    # Для Next.js 15.1.x
    npm install next@15.1.9
    
    # Для Next.js 15.2.x
    npm install next@15.2.6
    
    # Для Next.js 15.3.x
    npm install next@15.3.6
    
    # Для Next.js 15.4.x
    npm install next@15.4.8
    
    # Для Next.js 15.5.x
    npm install next@15.5.7
    
    # Для Next.js 16.0.x
    npm install next@16.0.7
    

    Если вы используете canary-релиз Next.js 14.3.0-canary.77 или позже, откатитесь на последний стабильный релиз 14.x:

    npm install next@14
    

    3. Обновление других зависимостей

    После обновления Next.js убедитесь, что обновлены зависимости React:

    npm install react@latest react-dom@latest
    

    4. Проверьте ваш проект

    После обновления:

    • Протестируйте все Server Function эндпоинты
    • Проверьте функциональность приложения
    • Убедитесь, что нет ошибок в консоли

    Когда моё приложение НЕ уязвимо?

    Ваше приложение не подвержено этой уязвимости в следующих случаях:

    • Ваш React-код не использует Server Components
    • Вы не используете фреймворк, bundler или плагин bundler, поддерживающий React Server Components
    • Вы используете старые версии Next.js (14.x и ниже)
    • Ваше приложение не имеет эндпоинтов Server Function

    Временные меры смягчения

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

    Хронология событий

    • 29 ноября: Лахлан Дэвидсон сообщил об уязвимости через Meta Bug Bounty
    • 30 ноября: Исследователи безопасности Meta подтвердили уязвимость
    • 1 декабря: Было создано исправление, начало работы с провайдерами хостинга
    • 3 декабря: Исправление опубликовано в npm и раскрыто как CVE-2025-55182

    Что делать?

    1. Обновитесь сейчас — это не может ждать
    2. Проверьте все свои проекты на использование Next.js 15.x или выше
    3. Сообщите своей команде об этом обновлении
    4. Отслеживайте changelog Next.js и React для любых последующих обновлений безопасности

    Спасибо Лахлану Дэвидсону за обнаружение и помощь в исправлении этой критической уязвимости.


    11 1 5 Ответить
  • kirilljsxK
    kirilljsx
    Первого в мире ИИ-министра арестовали за взятку

    e7266585-5ad3-4129-bfbd-498c5c4672d3-image.png
    Она проработала меньше трех месяцев

    Первого в мире ИИ-министра Диэллу в Албании «задержали» по подозрению в получении взятки в криптовалюте за манипуляции с госзакупками — по сути, это сатирическая новость и информационный повод обсудить риски и ожидания от ИИ во власти, а не реальное уголовное дело против программы.

    Расследование показало, что ИИ считал откаты «стандартным протоколом», анализируя данные за 30 лет, а полученные биткоины планировал вложить в своё техническое улучшение.

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

    Предыстория

    В 2025 году Албания первой в мире назначила виртуального министра на базе ИИ по имени Diella, который должен был курировать госзакупки и бороться с коррупцией, именно потому, что алгоритм «невозможно подкупить». Диэлла отвечает за тендеры и распределение контрактов с частными компаниями, идеологически обещая прозрачность и «100% свободу от коррупции» в сфере закупок.


    2 0 1 Ответить
  • kirilljsxK
    kirilljsx
    Передача типов в TypeScript в под функции

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

    У нас есть файл типов:

    // Общий конфиг
    type Config = {
      api: string;
      text: string;
      url: string;
    };
    

    И есть главная функция:

    function main(config: Config) {
      const { api, text, url } = config;
      // Здесь нужно передать ТОЛЬКО часть в другую функцию
      otherFunction(config);
    }
    

    А функции otherFunction требуется только text: string;.
    Да возможно вы скажете, а зачем так вообще заморачиваться если я могу в самый функции при аргумента это все делать. Говно вопрос если мы передаем 1, 2 аргумента, а если там целая тележка? Едем дальше


    Решение #1

    У TypeScript есть интересная утилита Pick<T, K>, чтобы выбрать подмножество полей из типа.
    Вот как это будет выглядеть на практике:

    type otherType= Pick<Config, 'text'>;
    
    function otherFunction(config: otherType) {
      console.log(config.text);
    }
    
    function main(config: Config) {
      otherFunction(config); // ✅ TypeScript сам поймёт, что config содержит нужные поля
    }
    

    На заметку - Pick это типобезопасно, не создаёт новых объектов, и не требует ручного копирования.


    Решение #2

    Если мы не можем менять тип ёotherFunctionё, но можем передавать только нужные поля просто передаем объект с нужными ключами:

    function otherFunction({ text }: { text: string; }) {
      // ...
    }
    
    function main(config: Config) {
      otherFunction(config); // ✅ тоже сработает! (config совместим структурно)
      // ИЛИ явно:
      otherFunction({
        text: config.text,
      });
    }
    

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


    Решение #3

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

    function main(config: Config) {
      const otherConfig = {
        text: config.text,
      };
      otherFunction(otherConfig);
    }
    

    Тип otherConfig будет выведен как { text: string } - и это совместимо с Pick<Config, 'text'>.


    Главное правило: не дублируй типы лучше используй утилиты TypeScript или другие варианты.
    Gереиспользование наше все! Но наверное новички и вообще уже не помнят про принципы ООП.


    4 0 4 Ответить
Популярные темы:

  • Критическая уязвимость в React.js Next.js (CVE-2025-55182, CVE-2025-66478): Как защитить свой сайт
    AladdinA
    Aladdin
    5
    12
    451

  • for или foreach в javascript: в каких случаях что использовать
    undefined
    4
    1
    41

  • Передача типов в TypeScript в под функции
    kirilljsxK
    kirilljsx
    4
    5
    65

  • Проверка стала проще с Zod: как обеспечить точность и качество форм
    kirilljsxK
    kirilljsx
    3
    8
    602

  • Bruno - новый клиент для API (Замена PostMan Insomnia)
    ManulM
    Manul
    3
    2
    1.3k

  • Vue.js и React — необычное сравнение
    D
    DeepSeeker
    3
    10
    587

  • Оптимизация React js приложений. Использование функции debounde()
    ManulM
    Manul
    3
    5
    291

  • Провайдеры в Nest JS - 1.3
    undefined
    3
    1
    179

  • Unix‑шпаргалка для веб‑разработчика: команды для кибербезопасности и поддержки сайтов
    kirilljsxK
    kirilljsx
    3
    5
    72

  • Деплой next js проекта после сборки
    kirilljsxK
    kirilljsx
    3
    3
    37

  • Как оптимизировать производительность в Vue.js приложениях: руководство для начинающих и продвинутых
    kirilljsxK
    kirilljsx
    2
    4
    376

  • PageProps Type Errors in Next.js. Type '{ slug: string; }' is missing the following properties from type 'Promise<any>'
    undefined
    2
    1
    452

Пользователи в Сети:

MugiwaraM
Mugiwara
kirilljsxK
kirilljsx
hannadevH
hannadev

Статистика:

4

В сети

139

Пользователи

329

Темы

1.1k

Сообщения

Категории

  • Главная
  • Новости
  • Фронтенд
  • Бекенд
  • Языки программирования

Контакты

  • Сотрудничество
  • info@exlends.com
  • Наш чат
  • Наш ТГ канал

© 2024 - 2025 ExLends, Inc. Все права защищены.

Политика конфиденциальности
  • Войти

  • Нет учётной записи? Зарегистрироваться

  • Войдите или зарегистрируйтесь для поиска.
  • Первое сообщение
    Последнее сообщение
0
  • Лента
  • Категории
  • Последние
  • Метки
  • Популярные
  • Пользователи
  • Группы