팰월드 서버 직접 만들기 3탄 - 브라우저 관리 페이지 (의존성 0개 Node)
작성일 2026-07-13 · SameOS Tools
1·2탄에서 서버를 열고 안전하게 운영하는 법까지 다뤘습니다. 그래도 저장이나 재시작 때마다 SSH(원격 서버에 접속해 명령을 입력하는 통로)로 들어가 명령어를 치는 건 여전히 번거롭고, 시중의 관리 도구는 대부분 영어인 데다 제 서버 구성에 딱 맞지도 않았고요. 그래서 브라우저에서 버튼만 눌러 서버를 관리하는 한국어 페이지를 직접 만들었습니다. Node(자바스크립트를 서버에서 실행해 주는 프로그램) 기본 기능만 써서 설치할 외부 패키지가 하나도 없습니다.
구조 - 비밀번호는 서버 밖으로 내보내지 않는다
구조는 브라우저 → 중계 서버(프록시) → 팰월드 REST API 세 단계입니다. 브라우저가 REST API를 직접 부르게 하면 관리자 비밀번호가 브라우저에 남는데, 이게 왜 위험한지 직접 겪었습니다. 처음 버전은 비밀번호를 브라우저에 저장했는데, 서버에서 비밀번호를 바꾸자 열려 있던 브라우저들이 옛 비밀번호로 접속을 무한 반복하며 오류 기록이 폭주하고 서버까지 버벅였습니다. 지금은 비밀번호가 서버의 설정 파일에만 존재하고, 브라우저는 중계 서버가 열어 주는 버튼만 누릅니다.
로그인 화면은 아예 만들지 않았습니다. 대신 관리 페이지를 127.0.0.1(그 컴퓨터 자신만 접근할 수 있는 내부 주소)에만 열어 두면 외부에서는 접근 자체가 불가능하고, 관리할 때만 SSH 터널(내 컴퓨터와 서버 사이에 전용 통로를 뚫는 명령: ssh -L 8280:127.0.0.1:8280 계정@서버)을 연 뒤 브라우저에서 http://127.0.0.1:8280 으로 들어갑니다. 도메인 주소로 열고 싶다면 그때는 반드시 검증된 인증 서비스(허용한 사람만 로그인시켜 주는 문지기)를 앞에 세워야 합니다. 다만 이 페이지는 서버를 끄고 설정까지 바꿀 수 있는 곳이라, 서버를 연 사람 혼자만 쓰는 것이 가장 안전합니다 - 친구들은 게임으로만 접속하면 됩니다.
기본 기능 - 상태, 공지, 저장, 종료
팰월드 REST API에는 서버 정보(/info), 접속자 목록(/players), 공지(/announce), 저장(/save), 종료(/shutdown) 창구가 있습니다. 관리 페이지의 기본은 이것들을 버튼으로 묶는 것입니다. 특히 종료 버튼은 2탄에서 다룬 "저장 없이 꺼지는 문제" 때문에 공지 → 저장 → 종료를 한 번에 실행하도록 묶어야 실수가 없습니다. 상태 카드는 몇 초에 한 번씩 서버 정보를 받아 와 접속 인원을 보여 줍니다.
// server.js - 서버 터미널에서 node server.js 로 실행, 외부 패키지 0개
const http = require('http');
const { execFile } = require('child_process');
const REST = 'http://127.0.0.1:8212/v1/api';
// 비밀번호는 서버 환경변수에만 존재 - 브라우저로는 절대 보내지 않는다
const AUTH = 'Basic ' + Buffer.from('admin:' + process.env.PAL_ADMIN_PASSWORD).toString('base64');
async function rest(path, method = 'GET', body) {
const res = await fetch(REST + path, {
method,
headers: { Authorization: AUTH, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) throw new Error('REST ' + res.status);
return res.json().catch(() => ({})); // shutdown 등은 응답 본문이 비어 있음
}
http.createServer(async (req, res) => {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
try {
if (req.url === '/api/status') { // 상태 카드용
const [info, players] = await Promise.all([rest('/info'), rest('/players')]);
return res.end(JSON.stringify({ info, players: players.players }));
}
if (req.url === '/api/stop' && req.method === 'POST') { // 안전 종료 묶음
await rest('/announce', 'POST', { message: '30초 뒤 서버를 종료합니다' });
await rest('/save', 'POST');
await rest('/shutdown', 'POST', { waittime: 30, message: '서버 종료' });
return res.end('{"ok":true}');
}
if (req.url === '/api/power-on' && req.method === 'POST') { // 꺼진 서버는 REST가 없다
execFile('docker', ['start', 'palworld']);
return res.end('{"ok":true}');
}
// 나머지 버튼과 화면(HTML/JS) 전달도 같은 패턴
res.statusCode = 404; res.end('{}');
} catch (e) {
res.statusCode = 502; res.end(JSON.stringify({ error: String(e) }));
}
}).listen(8280, '127.0.0.1'); // 로컬호스트 전용 - 이 한 줄이 인증을 대신한다
붙여넣고 실행하기
위 코드를 저장하고 실행하는 순서입니다. 우분투에 apt로 설치되는 Node는 버전이 낡아서 이 코드(18 이상 필요)가 안 돌아가니, 공식 저장소를 등록해 설치합니다. 실행 후에는 3탄 앞부분에서 설명한 SSH 터널로 접속합니다.
# 1) Node 18 이상 설치 (우분투·데비안)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node --version # v22.x가 나오면 성공
# 2) 코드 저장 후 실행 (1탄에서 만든 작업 폴더에서)
cd ~/palworld
nano server.js # 위 코드 붙여넣기 → Ctrl+O, Enter, Ctrl+X
export $(grep -v '^#' .env | xargs)
PAL_ADMIN_PASSWORD="$ADMIN_PASSWORD" node server.js
# 3) 내 컴퓨터에서 - SSH 터널을 열고 브라우저로 접속
ssh -L 8280:127.0.0.1:8280 계정@서버주소
# 열어 둔 채로 브라우저에서 http://127.0.0.1:8280
여기까지가 기본편입니다. 이제 저장과 종료가 터미널 명령이 아니라 버튼 하나입니다. 4탄에서는 REST API만으로는 안 되는 설정 편집과 전원 켜기/끄기, 그리고 만들고 나니 가장 재미있었던 서버 콘솔과 실시간 지도를 붙입니다.