마인크래프트 멀티서버를 웹 대시보드로 관리하기
메인/야생/건축 3개 서버를 한 화면에서 시작·정지·로그·상점까지 관리하는 대시보드를 만든 기록.
마인크래프트 서버 하나 운영하는 것도 귀찮은데 세 개를 굴리려니 답이 없었다. SSH 들어가서 screen -r mc, 다른 SSH 창 띄워서 screen -r minecraft-wild, 또 띄워서 screen -r minecraft-creative… 이러다가 포트 충돌나고 누가 어디 들어갔는지 모르고 아무도 안 들어와서 잊어버린다.
그래서 한 화면에서 다 보이게 만들었다.
서버 구성
세 개의 마인크래프트 서버를 한 VM(vm1.lab-it.net, 172.30.1.110) 안에 띄운다.
| 서버 | 포트 | 경로 | screen 이름 | 용도 |
|---|---|---|---|---|
| 메인 | 25565 | /opt/minecraft | mc | 허브 + 상점/경제 |
| 야생 | 25566 | ~/servers/wild | minecraft-wild | 생존 |
| 건축 | 25567 | ~/servers/creative | minecraft-creative | 크리에이티브 |
각각 별도 프로세스로 돌고, screen 세션으로 백그라운드 분리. 대시보드 백엔드가 이 세 서버를 추상화한다.
아키텍처
브라우저
│ HTTPS (Cloudflare Tunnel)
▼
nginx (dist 정적 서빙) ── /api/* ──▶ FastAPI 백엔드 (:8002)
│
├─▶ screen -S mc -X stuff "say ..."
├─▶ tail -f logs/latest.log
├─▶ rcon (포트별)
└─▶ SQLite (상점/경제)
- 백엔드:
/home/minecraft/Project/minecraft-dashboard/main.py(FastAPI) - 프론트:
/home/minecraft/Project/minecraft-dashboard-frontend/src/App.jsx(Vite + React) - 외부 노출: Cloudflare Tunnel로만. 직접 포트 미오픈.
API 설계
핵심은 모든 엔드포인트에 server_id 쿼리 파라미터가 붙는다는 점이다. 한 백엔드가 세 서버를 다룬다.
GET /servers — 전체 서버 현황 (online/offline/플레이어 수)
POST /servers/{id}/start — 서버 시작
POST /servers/{id}/stop — 서버 정지
GET /logs/list?server_id=main — 로그 파일 목록
GET /logs/view?server_id=... — 특정 로그 (gzip 압축 자동 풀기)
GET /logs/search?q=... — 키워드 검색
GET /shop/items — 상점 아이템 CRUD
POST /shop/items
GET /shop/economy?player=... — 플레이어 코인 조회/수정
server_id는 main | wild | creative. 백엔드가 dict로 서버별 경로/포트/screen 이름을 들고 있다.
SERVERS = {
"main": {"path": "/opt/minecraft", "port": 25565, "screen": "mc"},
"wild": {"path": "/home/minecraft/servers/wild", "port": 25566, "screen": "minecraft-wild"},
"creative": {"path": "/home/minecraft/servers/creative", "port": 25567, "screen": "minecraft-creative"},
}
환경변수 MC_SCREEN으로 메인 서버 screen 이름은 외부에서 덮어쓸 수 있게 했다 — 다른 사람이 설치할 때 mc가 이미 점유된 경우 대비.
서버 시작/정지
서버를 시작하는 가장 간단한 방법은 그냥 screen 세션 안에서 java -jar 띄우는 것이다. 데몬화 X, systemd unit X. 죽으면 그냥 죽고 살릴 때만 살린다.
def start_server(server_id: str):
cfg = SERVERS[server_id]
# screen 세션이 이미 있으면 무시
if subprocess.run(["screen", "-S", cfg["screen"], "-Q", "select", "."]).returncode == 0:
raise HTTPException(409, "이미 실행 중")
subprocess.Popen([
"screen", "-dmS", cfg["screen"],
"bash", "-c",
f"cd {cfg['path']} && java -Xmx4G -jar server.jar nogui"
])
정지는 더 단순하다. screen에 stop 명령을 쏜다.
def stop_server(server_id: str):
cfg = SERVERS[server_id]
subprocess.run([
"screen", "-S", cfg["screen"],
"-X", "stuff", "stop\n"
])
stuff 명령은 screen의 입력 버퍼에 문자열을 그대로 밀어넣는다. 마인크래프트 콘솔이 stop을 받으면 정상 종료된다.
상점/경제 시스템
이게 사실 제일 만들기 귀찮았다. 마인크래프트 본체에 있는 게 아니라 외부 SQLite + 게임 안에서 명령어로 조작하는 구조.
CREATE TABLE shop_items (
id INTEGER PRIMARY KEY,
item_id TEXT NOT NULL, -- minecraft:diamond
display_name TEXT,
price INTEGER NOT NULL,
stock INTEGER DEFAULT -1 -- -1 = 무한
);
CREATE TABLE economy (
player TEXT PRIMARY KEY,
coin INTEGER DEFAULT 0
);
대시보드에서 아이템 추가/가격 수정/재고 관리. 게임 내에서 NPC가 say 명령으로 가격 안내, 플레이어가 /buy diamond 같은 커스텀 명령(스크립팅 플러그인) 치면 백엔드 API를 때려서 코인 차감 + 아이템 지급.
로그 뷰어
이게 의외로 유용하다. 다인 서버 운영하면 누가 누구한테 욕했는지, 누가 야간에 다이아 풀씨 캤는지 추적해야 할 일이 생긴다. SSH 안 들어가도 브라우저에서 검색 가능.
@app.get("/logs/view")
def view_log(server_id: str, file: str):
cfg = SERVERS[server_id]
log_path = Path(cfg["path"]) / "logs" / file
if not log_path.is_relative_to(Path(cfg["path"]) / "logs"):
raise HTTPException(403, "경로 탈출 차단")
if file.endswith(".gz"):
with gzip.open(log_path, "rt") as f:
return PlainTextResponse(f.read())
return PlainTextResponse(log_path.read_text())
is_relative_to로 path traversal 차단. 마인크래프트는 일자별 로그를 logs/2026-05-09-1.log.gz로 압축 보관하는데 자동으로 풀어서 응답.
안 만든 것 (의도적으로)
- 인증. Cloudflare Tunnel + Zero Trust 정책으로 외부 노출은 내가 인증된 디바이스에서만. 백엔드 자체엔 로그인 없음. 단순함이 안전함보다 우선이라 못 하는 게 아니라 — Tunnel 뒤에 있어서 안 해도 됐다.
- systemd 자동 재시작. 마인크래프트 서버가 죽으면 그냥 죽게 둠. 누가 들어와서 시작 누르면 살아남.
- 권한 분리. 누가 누가 어느 서버를 시작/정지할 수 있는지 — 안 나눔. 운영자 본인만 접근.
만들면서 알게 된 것들
screen은 의외로 견고하다. systemd unit + Java tuning으로 깔끔하게 가는 게 정석이지만, screen + stuff로 명령 주입하는 게 구현 비용이 압도적으로 작다. 마이너 서비스에 적합.
server.jar 경로 통일. 야생/건축 서버 셋업 시 server.jar로 통일해서 저장하면 백엔드가 자동 인식. spigot, paper, purpur 어떤 걸 받든 파일명만 같으면 됨.
FastAPI는 이런 작은 컨트롤 패널에 가장 잘 맞는다. 자동 OpenAPI 문서 + Pydantic 검증 + dict 기반 라우팅이 이런 운영 도구에 안성맞춤.
스택 정리
| 구성 | 기술 |
|---|---|
| 백엔드 | Python 3.12, FastAPI 0.136, SQLite |
| 프론트엔드 | Vite + React (JSX), 정적 빌드 |
| 프로세스 관리 | GNU screen |
| 서빙 | nginx (정적), Cloudflare Tunnel |
| 통신 | screen stuff 명령 주입 |
서버 운영은 결국 자기 자신을 위한 도구를 짧게 만드는 일이다. 화려한 대시보드보다 자기 손에 맞는 단축키 한 줄이 낫다.