카테고리: 디스코드 봇 만들기

라즈베리파이로 디스코드 봇 24시간 돌리기 - 제로 2 W·4·5 뭘 살까? 설치부터 systemd 자동 실행까지 🍓

목차31개 항목

안녕하세요! Mojangcam입니다 😎

봇 만드는 글을 스물세 편이나 쓰는 동안, 댓글이랑 검색 유입으로 제일 많이 들어온 질문이 이거예요.

"내 컴퓨터 끄면 봇도 죽는데, 이거 어떻게 24시간 켜두나요?"

지금까지 두 가지 답을 드렸었죠.

오늘은 세 번째 선택지예요. 한 번 사두면 월세가 안 나가는 방법, 라즈베리파이입니다 🍓

이번 글에서 배울 것

  • 전기요금 계산 — 진짜로 VPS보다 싼가? (실제 숫자로)
  • 제로 2 W vs 파이 4 vs 파이 5, 봇 돌리는 데 뭘 사야 하나
  • ⚠️ 2026년 라즈베리파이 가격이 이상해진 이유 (이거 모르고 사면 손해봐요)
  • 라즈베리파이 OS 설치 — 모니터·키보드 없이(헤드리스) 세팅하기
  • pip install 이 막히는 함정 — externally-managed-environment
  • systemd 로 자동 실행·자동 재시작 등록하기 (오늘의 핵심)
  • 봇이 며칠 뒤 조용히 죽는 4가지 원인과 예방법

한 번 세팅해두면 정전 나도 알아서 다시 켜지고, 봇이 죽어도 10초 뒤에 알아서 살아나요. 시작해볼게요!

💰 진짜 싼가? 전기요금부터 계산해봤어요

"전기요금 더 나오는 거 아니에요?" 이거 먼저 털고 갈게요.

라즈베리파이는 유휴 상태 소비전력이 W(와트) 단위예요. 봇은 대부분의 시간을 그냥 대기하니까 유휴 전력이 곧 실사용 전력이라고 봐도 됩니다.

모델 유휴 소비전력 한 달(24시간×30일) 전기요금(대략)
제로 2 W 약 0.6W 0.43 kWh 월 100원 안팎
파이 4 약 3~4W 2.2~2.9 kWh 월 300~600원
파이 5 약 2.7~3W 1.9~2.2 kWh 월 300~500원

💡 전기요금은 주택용 누진구간에 따라 달라져요. kWh당 120~215원으로 잡고 계산한 값이라 정확한 금액이 아니라 자릿수로 봐주세요. 핵심은 "한 달에 커피값도 안 나온다"는 거예요 ☕

VPS는 가장 싼 게 월 5천 원 정도예요. 파이 5를 10만 원에 샀다고 쳐도 약 1년 8개월이면 본전이고, 그 뒤로는 계속 이득이에요. 게다가 파이는 봇만 돌리는 게 아니라 예약 실행 자동화, 크롤링, NAS까지 같이 시킬 수 있고요.

단점도 솔직히 말씀드릴게요.

  • 우리 집 인터넷이 끊기면 봇도 끊겨요 (VPS는 데이터센터라 이럴 일이 거의 없죠)
  • 정전 나면 꺼져요 (부팅 시 자동 실행은 오늘 설정해둘 거예요)
  • 초기 비용이 한 번에 나가요

🎯 정리하면 — 개인·소규모 서버 봇이면 라즈베리파이가 정답, 수백 개 서버에 들어간 봇이면 VPS가 맞아요.

🍓 어떤 모델을 사야 할까요?

여기서부터가 오늘 글에서 제일 중요한 부분이에요. 2026년엔 특히요.

⚠️ 먼저 알아야 할 것 — 2026년 라즈베리파이 가격 대란

AI 데이터센터가 메모리를 쓸어가면서 LPDDR4 가격이 폭등했어요. 라즈베리파이 재단은 2025년 12월부터 2026년까지 세 번이나 가격을 올렸습니다. 2026년 2월 인상분만 봐도 이래요.

메모리 용량 인상액
1GB 인상 없음
2GB +$10
4GB +$15
8GB +$30
16GB +$60

여기서 우리한테 중요한 포인트가 두 개예요.

  • 1GB 모델은 가격을 지켰어요. 파이 4 1GB는 $35, 2025년 12월에 나온 파이 5 1GB는 $45 그대로예요
  • 제로 시리즈와 파이 3는 아예 영향이 없어요. 이 모델들은 구형 LPDDR2를 쓰는데, 재단이 몇 년치 재고를 갖고 있거든요

🔥 그러니까 지금은 "봇 돌리려고 8GB 사는" 게 제일 손해 보는 선택이에요. 봇은 램을 거의 안 씁니다. 이유는 바로 아래에서 숫자로 보여드릴게요.

봇은 램을 얼마나 쓸까요? (실측)

말로만 "적게 쓴다"고 하면 안 믿기시죠. 제가 직접 재봤어요. discord.py 2.7.1을 올린 봇의 메모리 사용량이에요.

텍스트
파이썬만 실행했을 때      :  9.8 MB
+ discord.py import 후    : 37.7 MB   (+27.9)
+ Bot 객체·슬래시 명령어  : 37.8 MB   (+0.1)

봇 하나가 40MB 남짓이에요. 제로 2 W의 512MB 안에서도 열 개는 돌릴 수 있는 양이죠.

📌 이 값은 리눅스 x86 환경에서 잰 거라 파이(ARM)에선 몇 MB 차이가 날 수 있어요. 하지만 "수십 MB 수준"이라는 자릿수는 그대로입니다.

추가 - 파이에서 구동시 PSS 40.3MB, RSS 48.4MB, Swap 0MB 입니다!

그래서 뭘 사면 되나요?

제로 2 W 파이 4 파이 5
CPU 4코어 1GHz (A53) 4코어 1.5GHz (A72) 4코어 2.4GHz (A76)
메모리 512MB 고정 1~8GB 1~16GB
유휴 전력 약 0.6W 약 3~4W 약 2.7~3W
랜선 포트 ❌ 와이파이만 ✅ 기가비트 ✅ 기가비트
USB 부팅 ❌ SD카드만 ✅ ✅ (NVMe도 가능)
2026 가격 영향 없음 1GB만 안전 1GB만 안전
봇 돌리기 ⭕ 충분 ◎ 넉넉 ◎ 과할 정도

제 추천은 이래요.

  • 🥇 봇 1~3개만 돌릴 거예요 → 제로 2 W. 512MB로 충분하고, 전기도 거의 안 먹고, 가격 인상도 안 겪었어요. 손바닥보다 작아서 공유기 옆에 붙여두면 끝
  • 🥈 봇 + 자동화 스크립트 + 가끔 다른 것도 → 파이 4 또는 파이 5의 1GB/2GB. 랜선을 꽂을 수 있다는 게 진짜 큰 장점이에요 (와이파이 끊김 걱정이 사라져요)
  • 🥉 음악 봇을 만들 거예요 → 파이 4 이상 필수. 음성 스트리밍은 CPU를 실제로 씁니다. 제로 2 W는 여기서 버거워요
  • ❌ 8GB·16GB → 봇 용도로는 돈 낭비예요. 지금은 특히요

💬 제로 2 W의 진짜 단점은 램이 아니라 속도예요. 1GHz A53이라 pip install 한 번에 몇 분씩 걸립니다. 돌아가는 건 문제없는데 세팅할 때 인내심이 좀 필요해요 😅

🧰 준비물

  • 라즈베리파이 본체 (제로 2 W / 4 / 5 중 하나)
  • microSD 카드 16GB 이상 — ⭐ 여기서 아끼지 마세요. 값싼 카드가 봇이 죽는 1등 원인이에요 (아래에서 설명)
  • 전원 어댑터 — 제로 2 W는 5V 2.5A, 파이 4는 5V 3A(USB-C), 파이 5는 5V 5A 정품 권장
  • SD카드 리더기가 달린 PC
  • 디스코드 봇 토큰 — 개발자 포털에서 발급받은 그거요. 토큰이나 인텐트 때문에 막히면 디스코드 봇 오류 해결 총정리 글을 먼저 보고 오세요
  • 모니터·키보드는 필요 없어요 👍 오늘은 헤드리스로 갑니다

⚡ 파이 5는 전원이 까다로워요. 5V 5A(25W) 정품 어댑터가 아니면 부팅할 때 "저전압" 경고가 뜨고, 심하면 USB 장치를 못 씁니다. 폰 충전기 꽂았다가 원인 모를 재부팅 반복하는 사례가 정말 많아요.

1단계 - 라즈베리파이 OS 설치 (모니터 없이!)

Imager 로 굽기

라즈베리파이 공식 사이트에서 Raspberry Pi Imager를 받아 설치하세요. SD카드를 꽂고 실행합니다.

  • 장치 선택 : 갖고 계신 모델을 고르세요
  • OS 선택 : Raspberry Pi OS (other) → Raspberry Pi OS Lite (64-bit)
  • 저장소 : SD카드

🎯 꼭 Lite 버전을 고르세요! 데스크톱 버전은 화면·브라우저 때문에 램을 수백 MB 잡아먹어요. 봇은 화면이 필요 없습니다. 제로 2 W는 Lite가 사실상 필수예요.

⭐ 여기가 헤드리스의 핵심 — 설정 미리 넣기

"다음"을 누르면 OS 커스터마이징 설정을 하겠냐고 물어봐요. 꼭 "설정을 편집하기"를 누르세요. 여기서 안 하면 모니터를 연결해야 합니다.

일반 탭

  • 호스트명 : pibot (나중에 pibot.local 로 접속해요)
  • 사용자 이름·비밀번호 : pi / 원하는 비밀번호
  • 무선 LAN 설정 : 집 와이파이 이름(SSID)과 비밀번호, 국가는 KR
  • 지역 설정 : 시간대 Asia/Seoul

서비스 탭

  • ☑️ SSH 사용 체크 → "비밀번호 인증 사용" 선택

이렇게 하고 굽기를 누르면 끝이에요. 카드를 빼서 파이에 꽂고 전원을 넣습니다.

⚠️ 제로 2 W는 2.4GHz 와이파이만 지원해요. 공유기가 5GHz 전용 SSID로 잡혀 있으면 아무리 기다려도 안 붙습니다. 2.4GHz SSID를 입력하세요.

2단계 - SSH 로 접속하기

파이에 전원을 넣고 1~2분 기다린 뒤(첫 부팅은 오래 걸려요), PC의 터미널(윈도우는 PowerShell)에서 접속합니다.

터미널
ssh pi@pibot.local

pibot.local 로 안 되면 공유기 관리 페이지에서 IP를 찾아 ssh pi@192.168.0.xx 처럼 접속하세요.

접속했으면 먼저 업데이트부터 합니다.

터미널
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y python3-venv python3-pip git

💡 제로 2 W면 여기서 커피 한 잔 하고 오세요. 정말 오래 걸립니다 ☕

파이썬 버전을 확인해볼게요.

터미널
python3 -V

2025년 10월부터 라즈베리파이 OS는 Debian 13 "Trixie" 기반이고, 여기 들어 있는 시스템 파이썬은 3.13이에요. discord.py 2.7.x 를 돌리기에 충분합니다.

3단계 - ⚠️ pip 이 막히는 함정 (externally-managed-environment)

여기서 90%가 한 번은 막혀요. 신나게 봇 라이브러리를 깔려고 하면 이런 게 뜹니다.

터미널
pip install discord.py
텍스트
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.

고장난 게 아니에요. Debian 12(Bookworm)부터 적용된 PEP 668 이라는 규칙이에요. pip으로 시스템 전체에 패키지를 깔면 OS가 관리하는 파이썬 패키지와 충돌해서 시스템이 깨질 수 있으니까, 아예 막아둔 거예요.

정답은 가상환경(venv) 입니다. venv 완벽 정리 글에서 다룬 그 venv 맞아요.

터미널
mkdir ~/pi-bot && cd ~/pi-bot
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install discord.py python-dotenv

프롬프트 앞에 (.venv) 가 붙으면 성공이에요.

터미널
python -c "import discord; print(discord.__version__)"
# 2.7.1

❌ --break-system-packages 는 쓰지 마세요. 검색하면 "이 옵션 붙이면 된다"는 글이 많은데, 이름 그대로 시스템 패키지를 깨뜨리는 옵션이에요. 나중에 apt upgrade 하다가 파이썬이 통째로 꼬입니다. venv가 정답이에요.

4단계 - 봇 코드 올리기

파일 만들기

nano 로 직접 만들어도 되고, PC에서 작성한 파일을 올려도 돼요.

터미널
# PC에서 파이로 파일 보내기 (PC 터미널에서 실행)
scp bot.py .env pi@pibot.local:~/pi-bot/

# 또는 깃허브에 올려뒀다면
cd ~/pi-bot && git clone https://github.com/내계정/내봇.git .

.env 에 토큰 넣기

터미널
nano ~/pi-bot/.env
텍스트
DISCORD_TOKEN=여기에_봇_토큰을_붙여넣으세요

Ctrl+O → Enter → Ctrl+X 로 저장하고 나옵니다. 그리고 권한을 반드시 조여주세요.

터미널
chmod 600 ~/pi-bot/.env

🔒 토큰은 비밀번호와 똑같아요. 깃허브에 실수로 올리면 봇이 통째로 털립니다. .gitignore 에 .env 를 꼭 넣으세요.

손으로 한 번 돌려보기

systemd에 등록하기 전에 반드시 손으로 실행해서 되는지 확인하세요.

터미널
cd ~/pi-bot
source .venv/bin/activate
python bot.py
텍스트
2026-08-15 21:04:11 [INFO] pi-bot: 슬래시 명령어 동기화 완료
2026-08-15 21:04:12 [INFO] pi-bot: MojangBot#1234 로그인 성공 (지연 78ms)

디스코드에서 /핑 을 쳐보고 응답이 오면 성공이에요! Ctrl+C 로 끄고 다음 단계로 갑니다.

5단계 - ⭐ systemd 로 자동 실행 등록하기 (오늘의 핵심)

지금 상태는 SSH를 끊으면 봇도 같이 죽어요. 이걸 리눅스의 서비스로 등록해서 "부팅하면 알아서 켜지고, 죽으면 알아서 살아나게" 만듭니다.

터미널
sudo nano /etc/systemd/system/discordbot.service
텍스트
[Unit]
Description=Discord Bot (Raspberry Pi)
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=pi
Group=pi
WorkingDirectory=/home/pi/pi-bot
ExecStart=/home/pi/pi-bot/.venv/bin/python /home/pi/pi-bot/bot.py
Restart=always
RestartSec=10
Environment=PYTHONUNBUFFERED=1
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

하나씩 뜯어볼게요 🔍

  • After=network-online.target : ⚠️ 여기 정말 중요해요. 흔히 쓰는 network.target 은 "네트워크 장치가 올라옴"까지만 보장하고 IP가 잡혔는지는 안 봐요. 그래서 부팅 직후 DNS 조회에 실패하면서 봇이 죽습니다. network-online.target 과 Wants= 를 둘 다 써야 제대로 기다려요
  • User=pi : 이걸 빼면 root로 실행돼요. 봇이 뚫렸을 때 피해가 커지니 반드시 일반 계정으로 돌리세요
  • ExecStart 의 절대경로 : ⭐ 1등 실수 지점. systemd는 source .venv/bin/activate 를 모르고 PATH도 우리 셸과 달라요. venv 안의 python을 절대경로로 직접 가리켜야 합니다
  • Restart=always + RestartSec=10 : 봇이 어떤 이유로든 죽으면 10초 뒤에 다시 켜요. 인터넷이 잠깐 끊겨서 죽어도 알아서 복구됩니다
  • PYTHONUNBUFFERED=1 : 파이썬은 출력을 모아뒀다 한 번에 내보내요. 이걸 안 켜면 로그가 한참 뒤에야 보여서 디버깅할 때 미쳐버립니다

이제 등록하고 켭니다.

터미널
sudo systemctl daemon-reload
sudo systemctl enable --now discordbot
sudo systemctl status discordbot
텍스트
● discordbot.service - Discord Bot (Raspberry Pi)
     Loaded: loaded (/etc/systemd/system/discordbot.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-08-15 21:10:33 KST; 8s ago
   Main PID: 1041 (python)
      Tasks: 4 (limit: 384)
        CPU: 3.221s
     CGroup: /system.slice/discordbot.service
             └─1041 /home/pi/pi-bot/.venv/bin/python /home/pi/pi-bot/bot.py

Active: active (running) 초록불이 뜨면 끝났어요! 이제 SSH를 끊어도, 정전이 나고 전원이 돌아와도 봇은 알아서 켜집니다 🎉

💡 enable 은 "부팅 시 자동 시작", start 는 "지금 켜기"예요. enable --now 는 둘을 한 번에 합니다.

자주 쓰는 명령어

터미널
sudo systemctl restart discordbot   # 코드 고친 뒤 재시작
sudo systemctl stop discordbot      # 잠깐 끄기
sudo systemctl disable discordbot   # 부팅 자동 시작 해제

6단계 - 로그 보기 (journalctl)

print 로 찍은 것도, 에러 메시지도 전부 journald가 모아둬요.

터미널
journalctl -u discordbot -f          # 실시간으로 따라가기 (제일 많이 씀)
journalctl -u discordbot -n 50       # 최근 50줄
journalctl -u discordbot --since "1 hour ago"
journalctl -u discordbot -p err      # 에러만

-f 는 tail -f 랑 같아요. 봇을 고치는 동안 이 창을 하나 띄워두면 편합니다.

7단계 - 며칠 뒤 조용히 죽지 않게 만드는 4가지

여기까지만 해도 돌아가요. 그런데 일주일쯤 지나면 봇이 소리소문 없이 멈춰 있는 경우가 있어요. 원인은 거의 이 넷 중 하나입니다.

① 와이파이 절전 모드 (제로 2 W에서 특히!)

라즈베리파이의 와이파이는 기본적으로 절전 모드가 켜져 있어요. 트래픽이 뜸하면 무선 칩이 살짝 조는데, 봇처럼 조용히 대기하는 프로그램은 이때 연결이 끊깁니다.

터미널
# 지금 상태 확인 (Power Management:on 이면 켜져 있는 것)
iwconfig wlan0 | grep -i power

# 즉시 끄기
sudo iw dev wlan0 set power_save off

재부팅해도 유지되게 서비스로 만들어둘게요.

터미널
sudo nano /etc/systemd/system/wifi-powersave-off.service
텍스트
[Unit]
Description=Disable WiFi power save
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/sbin/iw dev wlan0 set power_save off

[Install]
WantedBy=multi-user.target
터미널
sudo systemctl enable --now wifi-powersave-off

🥇 랜선을 꽂을 수 있다면 그게 최고의 해결책이에요. 파이 4·5를 추천한 이유가 바로 이겁니다.

② SD카드 수명 — 로그가 카드를 갉아먹어요

microSD는 쓰기 횟수에 수명이 있어요. 봇 로그가 24시간 계속 쌓이면 싸구려 카드는 몇 달 만에 맛이 갑니다. 로그 용량에 상한을 걸어두세요.

터미널
sudo nano /etc/systemd/journald.conf
텍스트
[Journal]
SystemMaxUse=50M
SystemMaxFileSize=10M
터미널
sudo systemctl restart systemd-journald

💾 카드는 정품 SanDisk·Samsung의 A1/A2 등급을 쓰세요. 이름 모를 저가 카드가 "봇이 갑자기 파일을 못 읽어요" 사태의 진짜 범인인 경우가 정말 많아요. 파이 4·5라면 아예 USB SSD로 부팅하는 게 수명·속도 둘 다 훨씬 낫습니다.

③ 하드웨어 워치독 — 파이가 통째로 얼었을 때

Restart=always 는 봇 프로세스가 죽었을 때만 살려요. 커널이 얼어버리면 systemd도 같이 멈추죠. 이럴 때 파이를 강제로 재부팅시키는 하드웨어 워치독이 있어요.

터미널
sudo nano /boot/firmware/config.txt

맨 아래에 추가합니다.

텍스트
dtparam=watchdog=on
터미널
sudo apt install -y watchdog
sudo systemctl enable --now watchdog
sudo reboot

⚠️ 파일 경로가 예전엔 /boot/config.txt 였는데 최근 OS에선 /boot/firmware/config.txt 로 바뀌었어요. 옛날 블로그 글 보고 따라 하다 "파일이 없다"고 헤매는 분이 많습니다.

④ 토큰 만료·인텐트 문제

이건 파이 문제가 아니라 봇 문제예요. 개발자 포털에서 토큰을 재발급하면 예전 토큰은 즉시 죽어요. 로그에 LoginFailure 가 보이면 .env 를 확인하세요. 자세한 건 디스코드 봇 오류 해결 총정리 글에 정리해뒀어요.

📦 전체 코드 (복붙 OK)

1) bot.py

Python
# bot.py - 라즈베리파이 24시간 구동용 최소 봇
# discord.py 2.7.1 / python-dotenv 1.x / Python 3.9+
# 라즈베리파이 OS Trixie(Debian 13) 기준 - 시스템 파이썬 3.13

import logging
import os
import platform
import sys
from pathlib import Path

import discord
from discord.ext import commands
from dotenv import load_dotenv

BASE_DIR = Path(__file__).resolve().parent
load_dotenv(BASE_DIR / ".env")

TOKEN = os.getenv("DISCORD_TOKEN")

# systemd 로 돌릴 땐 print 대신 logging 을 쓰세요.
# stdout 으로 나간 로그를 journald 가 그대로 주워 담습니다.
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    stream=sys.stdout,
)
log = logging.getLogger("pi-bot")

intents = discord.Intents.default()
intents.message_content = True          # 개발자 포털에서도 켜야 해요!

class PiBot(commands.Bot):
    def __init__(self):
        super().__init__(command_prefix="!", intents=intents)

    async def setup_hook(self):
        await self.tree.sync()
        log.info("슬래시 명령어 동기화 완료")

bot = PiBot()

@bot.event
async def on_ready():
    log.info("%s 로그인 성공 (지연 %.0fms)", bot.user, bot.latency * 1000)

@bot.tree.command(name="핑", description="봇 응답 속도를 확인해요")
async def ping(interaction: discord.Interaction):
    await interaction.response.send_message(f"퐁! {bot.latency * 1000:.0f}ms")

@bot.tree.command(name="파이상태", description="라즈베리파이 상태를 보여줘요")
async def pi_status(interaction: discord.Interaction):
    embed = discord.Embed(title="라즈베리파이 상태", color=0xC51A4A)
    embed.add_field(name="모델", value=read_model(), inline=False)
    embed.add_field(name="CPU 온도", value=f"{read_cpu_temp():.1f}도", inline=True)
    embed.add_field(name="메모리", value=read_memory(), inline=True)
    embed.add_field(name="가동 시간", value=read_uptime(), inline=True)
    embed.set_footer(text=f"Python {platform.python_version()}")
    await interaction.response.send_message(embed=embed)

def read_model() -> str:
    """/proc/device-tree/model 에 보드 이름이 그대로 들어 있어요."""
    try:
        raw = Path("/proc/device-tree/model").read_bytes()
        return raw.decode("utf-8").rstrip("\x00")
    except OSError:
        return "알 수 없음 (라즈베리파이가 아님)"

def read_cpu_temp() -> float:
    """밀리도(1/1000도) 단위라 1000 으로 나눠야 해요."""
    try:
        milli = Path("/sys/class/thermal/thermal_zone0/temp").read_text()
        return int(milli.strip()) / 1000
    except (OSError, ValueError):
        return 0.0

def read_memory() -> str:
    """psutil 없이 /proc/meminfo 로 읽어요. Zero 2 W 에선 이 몇 MB가 아깝습니다."""
    try:
        info = {}
        for line in Path("/proc/meminfo").read_text().splitlines():
            key, _, value = line.partition(":")
            info[key] = int(value.strip().split()[0])       # kB
        total = info["MemTotal"] / 1024
        available = info["MemAvailable"] / 1024
        return f"{total - available:.0f} / {total:.0f} MB"
    except (OSError, KeyError, ValueError):
        return "알 수 없음"

def read_uptime() -> str:
    try:
        seconds = float(Path("/proc/uptime").read_text().split()[0])
    except (OSError, ValueError):
        return "알 수 없음"
    days, rest = divmod(int(seconds), 86400)
    hours, rest = divmod(rest, 3600)
    minutes = rest // 60
    if days:
        return f"{days}일 {hours}시간 {minutes}분"
    return f"{hours}시간 {minutes}분"

if __name__ == "__main__":
    if not TOKEN:
        log.error(".env 에 DISCORD_TOKEN 이 없습니다. 종료합니다.")
        raise SystemExit(1)
    bot.run(TOKEN, log_handler=None)    # 위에서 만든 logging 설정을 그대로 사용

💡 /파이상태 명령어는 psutil 같은 추가 라이브러리 없이 리눅스의 /proc, /sys 파일을 직접 읽어요. 파이에서 아낄 수 있는 건 다 아끼는 게 좋으니까요. 파이가 아닌 곳에서 돌리면 모델 이름 대신 안내 문구가 나오게 해뒀습니다.

2) 폴더 구조

텍스트
/home/pi/pi-bot/
├─ .venv/                 # 가상환경 (python3 -m venv .venv)
├─ .env                   # DISCORD_TOKEN (chmod 600!)
├─ .gitignore             # .venv/, .env
├─ bot.py
└─ requirements.txt

3) requirements.txt

텍스트
discord.py==2.7.1
python-dotenv==1.1.1

새 파이에 옮길 땐 이거 한 줄이면 끝이에요.

터미널
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt

🚨 자주 나는 에러

1. systemctl status 에 status=203/EXEC 가 떠요

  • 원인 : ExecStart 경로가 틀렸어요. 1등 원인입니다
  • 해결 : ls -l /home/pi/pi-bot/.venv/bin/python 로 파일이 진짜 있는지 확인하세요. 사용자 이름이 pi 가 아니라면 경로도 같이 바꿔야 해요

2. ModuleNotFoundError: No module named 'discord'

  • 원인 : systemd가 시스템 파이썬으로 실행했어요. venv 밖에는 discord.py가 없죠
  • 해결 : ExecStart 를 /usr/bin/python3 가 아니라 .venv/bin/python 으로 바꾸세요

3. 부팅 직후에만 죽고, 손으로 켜면 잘 돼요

  • 원인 : 와이파이가 붙기 전에 봇이 먼저 떠서 DNS 조회에 실패했어요
  • 해결 : Wants=network-online.target 과 After=network-online.target 을 둘 다 넣으세요. 그래도 불안하면 ExecStartPre=/bin/sleep 15 를 추가해 여유를 주면 확실합니다

4. error: externally-managed-environment

  • 원인 : PEP 668. 시스템 파이썬에 직접 설치하려고 했어요
  • 해결 : venv를 만들어 그 안에서 설치하세요. --break-system-packages 로 우회하지 마세요

5. .env 를 읽어도 토큰이 None 이에요

  • 원인 : systemd는 WorkingDirectory 기준으로 도는데, load_dotenv() 를 인자 없이 부르면 현재 폴더에서 .env 를 찾아요
  • 해결 : 위 코드처럼 load_dotenv(BASE_DIR / ".env") 로 파일 위치를 명시하세요. 스크립트 옆에 있는 .env 를 확실하게 집습니다

6. 로그가 실시간으로 안 보여요

  • 원인 : 파이썬 출력 버퍼링
  • 해결 : Environment=PYTHONUNBUFFERED=1 을 서비스 파일에 추가하세요

7. 화면에 "Undervoltage detected" 경고가 떠요

  • 원인 : 전원이 부족해요. 특히 파이 5 + 폰 충전기 조합
  • 해결 : 정품 어댑터를 쓰세요 (파이 5는 5V 5A). 저전압 상태에선 SD카드 오류·랜덤 재부팅이 계속 납니다

8. 제로 2 W에서 pip install 이 멈춘 것 같아요

  • 원인 : 안 멈췄어요. 진짜로 느린 거예요 😅 빌드가 필요한 패키지면 수십 분도 걸립니다
  • 해결 : 기다리세요. 자꾸 그러면 pip install --prefer-binary 로 미리 빌드된 휠을 우선 받게 하거나, 스왑을 늘리세요

9. ssh pi@pibot.local 이 안 돼요

  • 원인 : mDNS(.local)가 네트워크에 따라 안 잡혀요
  • 해결 : 공유기 관리 페이지에서 파이의 IP를 찾아 IP로 접속하세요. 찾았으면 고정 IP로 할당해두면 편해요

💬 마무리

오늘은 봇을 내 손 안의 서버에 올렸어요 🍓

오늘의 요약

  • 전기요금은 월 100~500원 수준 — VPS 1년 반이면 본전이에요
  • 봇 하나는 램을 40MB 남짓 써요. 8GB 살 필요 전혀 없습니다. 2026년엔 특히 1GB·제로 시리즈가 가격 방어가 잘 돼 있어요
  • OS는 Lite 64-bit, Imager에서 SSH·와이파이를 미리 넣어 헤드리스로 세팅
  • pip 이 막히면 그건 고장이 아니라 PEP 668 — 답은 venv
  • systemd의 핵심 3종 : venv python 절대경로, network-online.target, Restart=always
  • 며칠 뒤 죽는 이유는 대개 와이파이 절전 아니면 SD카드예요

다음 #24편에서는 서버 로그·감사(Audit) 채널을 만들어요. 오늘 파이에 올린 봇이 24시간 지켜보면서 메시지 삭제·수정, 입퇴장을 전부 임베드 로그로 남길 거예요. #23편에서 만든 DB에 로그 채널 설정을 저장하면서 바로 써먹습니다. 기대해주세요! 🙌

🍓 파이를 사실 거면 본체보다 전원 어댑터와 SD카드에서 아끼지 마세요. 오늘 정리한 문제의 절반은 이 둘에서 나옵니다. 장비 고르는 기준은 코딩용 노트북 고르는 기준 글처럼 따로 정리해볼게요.


📚 봇을 24시간 돌리는 세 가지 방법

📚 디스코드 봇 만들기 시리즈

글쓴이

Mojangcam

직접 코드를 짜고, 서버를 돌리고, 막힌 에러를 해결하며 배운 것들을 기록하는 개발·IT 실전 기록 블로그