← ~/notes · 3 min read

마인크래프트 멀티서버를 웹 대시보드로 관리하기

메인/야생/건축 3개 서버를 한 화면에서 시작·정지·로그·상점까지 관리하는 대시보드를 만든 기록.

목차
  1. 서버 구성
  2. 아키텍처
  3. API 설계
  4. 서버 시작/정지
  5. 상점/경제 시스템
  6. 로그 뷰어
  7. 안 만든 것 (의도적으로)
  8. 만들면서 알게 된 것들
  9. 스택 정리

마인크래프트 서버 하나 운영하는 것도 귀찮은데 세 개를 굴리려니 답이 없었다. 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/minecraftmc허브 + 상점/경제
야생25566~/servers/wildminecraft-wild생존
건축25567~/servers/creativeminecraft-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 명령 주입

서버 운영은 결국 자기 자신을 위한 도구를 짧게 만드는 일이다. 화려한 대시보드보다 자기 손에 맞는 단축키 한 줄이 낫다.