Переменные окружения и секреты

Учим Цыплёнка-помощника прятать свой токен так, чтобы его не подобрал случайный человек из интернета — и чтобы бот при этом спокойно запускался и на твоём ноуте, и на сервере.

Переменные окружения — это настройки и секреты (например токен), которые хранят вне кода и подставляют при запуске программы. Бот читает их из окружения, а не из текста файла, поэтому секрет нигде не записан буквами рядом с логикой.

Зачем это вообще нужно

Представь, что ты гордо выкладываешь своего Цыплёнка на GitHub — пусть друзья посмотрят, какой крутой бот у тебя получился. Проходит час, ты открываешь чат, а бот ведёт себя странно: рассылает рекламу казино незнакомым людям, банит тебя из твоей же группы, меняет имя на что-то неприличное. Что случилось? Кто-то зашёл в твой репозиторий, увидел прямо в коде строчку bot = Bot("123456:AAH..."), скопировал этот токен и теперь управляет твоим ботом вместо тебя.

Звучит как страшилка, но это происходит постоянно. По интернету ползают боты-сканеры, которые круглыми сутками ищут на GitHub чужие токены и ключи. Нашли — и через минуту твой бот уже работает на чужого дядю. Telegram даже специально следит за этим: если он замечает, что токен утёк в публичный репозиторий, он его молча отзывает, и твой бот просто перестаёт отвечать.

Вспомни первый урок, где мы собирали первого бота на /start и вписывали токен прямо в код — для самого начала так было нагляднее. Но теперь, когда мы готовим Цыплёнка к настоящему запуску на сервере, пора навести порядок. Сегодня мы научимся хранить токен и другие секреты отдельно от кода — так, чтобы их можно было спокойно показать всему миру, а секрет при этом остался у тебя.

Вот к чему придём. В коде будет вот так:

bot = Bot(os.getenv("BOT_TOKEN"))

Ни одной секретной буквы. А сам токен будет лежать в отдельном файле .env, который никогда не попадёт ни в GitHub, ни в чужие руки. Поехали разбираться.

Что такое переменные окружения

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

Переменные окружения — это ровно такие подсказки, развешанные вокруг программы операционной системой. Программа при запуске может их прочитать: «какой у меня токен?», «к какой базе подключаться?», «я сейчас на боевом сервере или на ноуте разработчика?». Сами значения живут не в коде, а в окружении — в той среде, где программу запустили. Отсюда и название.

Это даёт сразу три выгоды, и все три тебе пригодятся:

  • Секрет не в коде. Токен лежит в окружении, а в коде только имя BOT_TOKEN. Имя показывать не страшно — это как сказать «мой пароль записан в блокноте», не показывая сам пароль.
  • Один код — разные настройки. На твоём ноуте бот берёт тестовый токен, на сервере — боевой. Код один и тот же, меняется только окружение. Не нужно перед каждым запуском лезть и править строчки.
  • Менять секрет легко. Если токен всё-таки утёк, ты получаешь у BotFather новый и подставляешь его в окружение. Код трогать не надо вообще.

Сам Python читает переменные окружения через модуль os из стандартной библиотеки. Главный инструмент — функция os.getenv("ИМЯ"): ты называешь имя переменной, она возвращает её значение (или None, если такой переменной в окружении нет). Давай пощупаем это руками.

Разбираемся по шагам

Шаг 1. Как Python читает окружение

Этот маленький сниппет можно запустить прямо здесь — он работает на чистой стандартной библиотеке и ничего секретного не содержит. Мы сами кладём переменную в окружение через os.environ, а потом читаем её обратно через os.getenv — чтобы увидеть оба конца цепочки.

import os

# обычно переменную ставит система или файл .env,
# но для демонстрации положим её прямо тут
os.environ["BOT_TOKEN"] = "123456:DEMO-TOKEN"

token = os.getenv("BOT_TOKEN")
print("Токен из окружения:", token)

# а вот переменной, которой нет, getenv вернёт None
missing = os.getenv("NETU_TAKOY")
print("Несуществующая:", missing)

# можно задать значение по умолчанию вторым аргументом
mode = os.getenv("MODE", "development")
print("Режим:", mode)

Вывод:

Токен из окружения: 123456:DEMO-TOKEN
Несуществующая: None
Режим: development

Разберём важное. os.environ — это словарь со всеми переменными окружения; обычно мы из него только читаем, но для демонстрации положили туда токен сами. os.getenv("BOT_TOKEN") достаёт значение по имени. Если переменной нет — возвращается None (а не ошибка!), и это важная ловушка, к которой мы ещё вернёмся. А второй аргумент у getenv — значение по умолчанию: os.getenv("MODE", "development") вернёт "development", если переменной MODE в окружении не оказалось. Удобно для необязательных настроек, но для токена так делать нельзя — у токена дефолта быть не может.

Шаг 2. Кладём секрет в файл .env

Прописывать переменные вручную в системе перед каждым запуском неудобно — забудешь одну, и бот не стартует. Поэтому придумали простой приём: складывать все секреты в отдельный текстовый файл .env (читается «дот-енв», от environment) в корне проекта. Формат проще некуда — строки вида ИМЯ=значение:

BOT_TOKEN=123456:AAHxYzReal-Token-From-BotFather
ADMIN_ID=987654321
WEATHER_API_KEY=abcdef0123456789
MODE=development

Результат: это не код, а просто список настроек. Имя переменной слева, значение справа, между ними знак = без пробелов. Кавычки вокруг значения не нужны — пиши токен как есть. Каждая настройка с новой строки.

Несколько правил, чтобы файл не подвёл:

  • Никаких пробелов вокруг =: пиши BOT_TOKEN=123, а не BOT_TOKEN = 123.
  • Имена принято писать заглавными буквами через подчёркивание: BOT_TOKEN, ADMIN_ID. Так сразу видно, что это переменная окружения.
  • Строки, начинающиеся с #, — это комментарии, их можно оставлять себе на память.

Сам по себе файл .env Python не читает — для этого нужна маленькая библиотека-помощник. Знакомься.

Шаг 3. Подключаем python-dotenv

Библиотека python-dotenv делает одну простую вещь: открывает файл .env, читает оттуда строки ИМЯ=значение и раскладывает их по os.environ — как будто ты задал эти переменные в системе сам. После этого обычный os.getenv их прекрасно видит. Сначала ставим библиотеку в терминале:

pip install python-dotenv

Результат: в твоё окружение Python добавится пакет dotenv, и его можно будет импортировать в коде бота.

Теперь подключаем её в bot.py — в самом верху, до того как мы создаём объект Bot. Это код нашего бота, поэтому он помечен как обычный текст: в браузере его не запустить, ему нужны установленные пакеты и настоящий токен.

import os
from dotenv import load_dotenv
from aiogram import Bot, Dispatcher

load_dotenv()  # читаем .env и кладём всё в окружение

BOT_TOKEN = os.getenv("BOT_TOKEN")
if BOT_TOKEN is None:
    raise RuntimeError("Не найден BOT_TOKEN! Проверь, что есть файл .env")

bot = Bot(BOT_TOKEN)
dp = Dispatcher()

Результат: при запуске bot.py сначала отработает load_dotenv() и перенесёт токен из .env в окружение. Затем os.getenv("BOT_TOKEN") достанет его и передаст в Bot(...). Если файла .env нет или в нём забыли BOT_TOKEN, бот не запустится молча, а честно крикнет: «Не найден BOT_TOKEN!». Объекты bot и dp — те же самые, что мы используем во всех уроках, поменялся только способ получения токена.

Обрати внимание на проверку if BOT_TOKEN is None. Без неё, если токен не подгрузился, в Bot(None) улетит пустота, и ошибка вылезет где-то глубоко внутри aiogram — поди разберись. А наша проверка ловит беду сразу и подсказывает, что чинить. Это маленькая забота о себе будущем.

Шаг 4. Прячем .env от Git через .gitignore

Мы вынесли токен в .env — но если этот файл случайно уедет в GitHub вместе с кодом, мы вернёмся ровно туда, откуда начали: секрет снова на виду. Нужно сказать Git: «этот файл не трогай, не сохраняй, не выгружай». Для этого есть специальный файл .gitignore — список того, что Git должен игнорировать. Создаём его в корне проекта (рядом с bot.py) и пишем внутри:

# секреты — ни в коем случае не в репозиторий
.env

# мусор Python
__pycache__/
*.pyc

# наша база, если она локальная
bot.db

Результат: теперь, когда ты делаешь git add ., Git видит в .gitignore строку .env и просто пропускает этот файл — он не попадёт ни в коммит, ни на GitHub. Твой код станет публичным, а секрет останется только у тебя на диске.

А как же тогда другой человек (или ты сам на сервере) поймёт, какие переменные боту нужны? Для этого рядом кладут .env.example — пустой образец без настоящих значений, который как раз коммитят в репозиторий:

BOT_TOKEN=сюда-вставь-токен-от-BotFather
ADMIN_ID=сюда-свой-id
WEATHER_API_KEY=сюда-ключ-погодного-сервиса

Результат: любой, кто скачает твой проект, увидит .env.example, скопирует его в .env и впишет свои настоящие секреты. Образец показывает какие переменные нужны, но не выдаёт их значений. Это вежливость по отношению к тем, кто будет запускать твоего бота, — в том числе к тебе на новом компьютере.

Частые ошибки и подводные камни

  • Закоммитить .env до того, как добавил его в .gitignore. Самая обидная ошибка: ты сначала сделал git add . и git commit с токеном внутри, а .gitignore добавил потом. Git уже запомнил файл, и игнор на него больше не действует. Хуже того — токен навсегда остаётся в истории коммитов, даже если ты удалишь файл следующим коммитом. Правило: .gitignore создавай первым, до первого коммита. А если секрет всё же утёк — не паникуй, просто сразу выпусти новый токен у BotFather, старый станет бесполезным.
  • Забыть вызвать load_dotenv(). Ты создал красивый .env, написал os.getenv("BOT_TOKEN"), запускаешь — а токен None. Причина почти всегда одна: забыл строчку load_dotenv() в начале файла. Без неё никто не прочитает .env, и окружение остаётся пустым. Запомни: сначала load_dotenv(), потом любые getenv.
  • Не проверять, что переменная вообще нашлась. os.getenv при отсутствии переменной возвращает не ошибку, а тихий None. Если не поставить проверку if BOT_TOKEN is None, бот попытается запуститься с пустым токеном и упадёт с непонятным сообщением далеко от настоящей причины. Лови пустоту сразу — и сэкономишь себе полчаса нервов.
  • Пробелы и кавычки в .env. Строка BOT_TOKEN = "123" с пробелами вокруг = и кавычками может прочитаться криво: в значение попадут лишние пробелы или сами кавычки. Пиши строго BOT_TOKEN=123 — без пробелов, без кавычек. Формат .env простой и капризный.
  • Скинуть .env другу в личку «чтобы запустил». Кажется безобидным, но мессенджер — это уже не «только у тебя на диске». Скриншот, переписка, утёкший аккаунт — и токен снова гуляет. Если другу нужно запустить бота, дай ему .env.example и пусть получит свой токен у BotFather для своей копии бота.

Мини-практика

Давай наведём порядок в проекте Цыплёнка по-настоящему. Выполни по шагам — это ровно то, что делают перед выкладкой любого бота:

  1. Создай .env. В корне проекта сделай файл .env и перенеси туда токен из кода: строка BOT_TOKEN=твой-настоящий-токен. Добавь заодно ADMIN_ID= со своим Telegram-id — пригодится в следующих уроках.
  2. Перепиши bot.py. Добавь вверху from dotenv import load_dotenv и вызов load_dotenv(), замени строку с токеном на os.getenv("BOT_TOKEN") и обязательно поставь проверку на None. Запусти бота и убедись, что он по-прежнему отвечает на /start.
  3. Защити секрет. Создай .gitignore со строкой .env, а рядом — .env.example с именами переменных без значений. Если проект уже под Git, проверь командой git status, что .env не показывается в списке на добавление.
  4. Со звёздочкой. Вынеси в .env ещё одну настройку — например MODE=development — и в коде через os.getenv("MODE", "production") сделай так, чтобы в режиме development бот при старте печатал в консоль «🐤 Запущен в тестовом режиме». Это первый шаг к разным настройкам для ноута и сервера.

Не спеши делать всё разом — сначала добейся, чтобы бот заводился от токена из .env, и только потом берись за .gitignore и режимы. Маленькими шагами надёжнее.

Итоги

Сегодня Цыплёнок-помощник повзрослел: он перестал носить свой пароль написанным на лбу и научился прятать его как положено. Ты разобрался, что переменные окружения — это подсказки, развешанные вокруг программы, а не вшитые в код; научился класть секреты в файл .env, читать их через python-dotenv и os.getenv, ловить пустоту проверкой на None и закрывать .env от Git через .gitignore с образцом .env.example для других.

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

В следующем уроке мы возьмём нашего аккуратно настроенного Цыплёнка и наконец-то поселим его на настоящий сервер, чтобы он работал круглые сутки, а не пока открыт твой ноутбук. И вот тут переменные окружения сыграют по-крупному: на сервере мы зададим токен прямо в окружении системы, без всякого .env — а код, который ты написал сегодня, заведётся там без единой правки. До встречи на сервере!

Проверьте себя
1. Почему токен нельзя писать прямо в коде бота, который выкладывают на GitHub?
AИз-за этого код медленнее работает
BЛюбой, кто увидит репозиторий, получит токен и сможет управлять твоим ботом
CTelegram берёт за это деньги
Daiogram не умеет читать токен из кода
2. Что вернёт os.getenv("BOT_TOKEN"), если такой переменной в окружении нет?
AПустую строку ""
BВызовет ошибку и остановит программу
CNone
DНоль
3. Что делает функция load_dotenv() из библиотеки python-dotenv?
AСоздаёт новый файл .env
BЧитает файл .env и раскладывает его переменные по окружению, чтобы их видел os.getenv
CШифрует токен перед отправкой в Telegram
DПодключает бота к серверу
4. Зачем добавлять строку .env в файл .gitignore?
AЧтобы Git не сохранял и не выгружал .env, и секреты не попали в репозиторий
BЧтобы бот запускался быстрее
CЧтобы python-dotenv нашёл файл
DЧтобы зашифровать токен
5. Файл .env случайно попал в коммит вместе с токеном. Что правильно сделать?
AНичего, токен в истории не страшен
BПросто удалить файл следующим коммитом — и всё чисто
CСразу выпустить новый токен у BotFather, потому что старый уже мог утечь
DУдалить весь репозиторий и начать заново
6. Для чего рядом с .env держат файл .env.example, который коммитят в репозиторий?
AЭто резервная копия настоящих секретов
BОн показывает, какие переменные нужны боту, но без настоящих значений
CЕго читает python-dotenv вместо .env
DЭто файл с настройками самого Git