Переменные окружения и секреты
Учим Цыплёнка-помощника прятать свой токен так, чтобы его не подобрал случайный человек из интернета — и чтобы бот при этом спокойно запускался и на твоём ноуте, и на сервере.
Переменные окружения — это настройки и секреты (например токен), которые хранят вне кода и подставляют при запуске программы. Бот читает их из окружения, а не из текста файла, поэтому секрет нигде не записан буквами рядом с логикой.
Зачем это вообще нужно
Представь, что ты гордо выкладываешь своего Цыплёнка на 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 для своей копии бота.
Мини-практика
Давай наведём порядок в проекте Цыплёнка по-настоящему. Выполни по шагам — это ровно то, что делают перед выкладкой любого бота:
- Создай
.env. В корне проекта сделай файл.envи перенеси туда токен из кода: строкаBOT_TOKEN=твой-настоящий-токен. Добавь заодноADMIN_ID=со своим Telegram-id — пригодится в следующих уроках. - Перепиши
bot.py. Добавь вверхуfrom dotenv import load_dotenvи вызовload_dotenv(), замени строку с токеном наos.getenv("BOT_TOKEN")и обязательно поставь проверку наNone. Запусти бота и убедись, что он по-прежнему отвечает на/start. - Защити секрет. Создай
.gitignoreсо строкой.env, а рядом —.env.exampleс именами переменных без значений. Если проект уже под Git, проверь командойgit status, что.envне показывается в списке на добавление. - Со звёздочкой. Вынеси в
.envещё одну настройку — напримерMODE=development— и в коде черезos.getenv("MODE", "production")сделай так, чтобы в режимеdevelopmentбот при старте печатал в консоль «🐤 Запущен в тестовом режиме». Это первый шаг к разным настройкам для ноута и сервера.
Не спеши делать всё разом — сначала добейся, чтобы бот заводился от токена из .env, и только потом берись за .gitignore и режимы. Маленькими шагами надёжнее.
Итоги
Сегодня Цыплёнок-помощник повзрослел: он перестал носить свой пароль написанным на лбу и научился прятать его как положено. Ты разобрался, что переменные окружения — это подсказки, развешанные вокруг программы, а не вшитые в код; научился класть секреты в файл .env, читать их через python-dotenv и os.getenv, ловить пустоту проверкой на None и закрывать .env от Git через .gitignore с образцом .env.example для других.
Главное, что ты унёс: секрет и код — это разные вещи, и жить они должны раздельно. Код можно показывать всему миру, секрет — только себе. Этот навык пригодится тебе не только в ботах: ровно так же прячут пароли от баз, ключи к платным сервисам и любые токены в любом серьёзном проекте.
В следующем уроке мы возьмём нашего аккуратно настроенного Цыплёнка и наконец-то поселим его на настоящий сервер, чтобы он работал круглые сутки, а не пока открыт твой ноутбук. И вот тут переменные окружения сыграют по-крупному: на сервере мы зададим токен прямо в окружении системы, без всякого .env — а код, который ты написал сегодня, заведётся там без единой правки. До встречи на сервере!