취미/자동차 및 전자기기

NAS 고도화 프로젝트 제 2탄 — 디스코드 봇

lazykuna 2026. 8. 3. 10:36

이번에는 NAS에 ubuntu 기반으로 discord를 연동시켜서, 좀 더 편하게 파일을 관리하는 솔루션을 만들어 보려고 한다.

간단한 파일 IO 정도의 작업이면 Synology의 Docker을 사용하여 Dockerfile로 배포하는 게 제일 깔끔하지만, 아무래도 VPN 다운로드 기능이나 기타등등의 알림 기능등이 들어갈 것이라서, NAS와는 독립적으로 고도화된 기능을 안정적으로 제공하기 위해 NAS와는 별도 Container을 쓰는 것으로 결정하였다.

작업은 크게 아래와 같았다.

  1. NAS에서 NFS 프로토콜 세팅
  2. Ubuntu 에서 NFS 폴더 마운트
  3. 디스코드 봇 생성 후 서버 초대
  4. 디스코드 봇 설정 후 기동
  5. 용례: Cloudflare WARP + 만화 다운로더 (선택)

이를 아키텍쳐로 그려내면 아래와 같다.

1. NAS에서 NFS 프로토콜 세팅 (NFS 서버 세팅)

먼저 NAS의 파일 시스템 접근을 위해 NFS로 마운트를 하려고 한다. 앞서서, NAS에서 NFS를 위한 설정을 해 주어야 하는데, NFS 프로토콜 접속 검증은 IP로 이루어지므로 Ubuntu 서버는 반드시 DHCP 고정되어 있어야 한다. Synology 기준 아래와 같은 설정을 한다.

먼저 제어판에 들어가서 File Services 메뉴를 들어가자

 

NFS 프로토콜 활성화를 시켜준다.

이제 NFS에 공유할 디렉터리와, 접속 가능한 클라이언트 IP 세팅을 해야 한다.

이번엔 제어판의 Shared Folder 항목으로 들어간다.

여기서 공유할 폴더를 선택 후 “Edit” 클릭

이제 허용할 IP와 권한을 적절히 넣어준다.

어차피 내부 IP만 허용해 줄 생각이었기 때문에, 그냥 192.168.219.0/24 로 열어주어도 될 것 같다. 하지만 NFS 서버 프로토콜은 강력한데 반해 보안은 허술하니까 적당한 선에서 타협을 하도록 하자.

이제 해당 폴더는 NFS 프로토콜에서 <ip>:/volume1/homes 로 접근 가능하다. /volume1 폴더가 하나 더 붙는 점 잊지 말자.

 

2. Ubuntu에서 NAS(NFS) 마운트

이제 NAS를 mount 하자. 아래 명령어로 작업이 수월하게 끝난다.

# NFS 관련 바이너리가 설치되지 않았다면, 먼저 설치할 것
sudo apt-get -y install nfs-common

sudo mount /mnt/nas
mount -t nfs <ip>:/volume1/homes /mnt/nas

마운트된 폴더 내의 내용물을 적절하게 볼 수 있으면 일단 성공. 이제 재부팅시에도 안정적으로 해당 폴더가 마운트될 수 있도록 /etc/fstab 에 자동 마운트를 설정해준다.

192.168.1.50:/mnt/nas  /mnt/local_nas  nfs  defaults,rw,hard,_netdev,nofail,x-systemd.automount  0  0

참고용으로 각 옵션의 자세한 의미:

defaults: Applies standard permissions and read/write settings.
rw: Mounts the remote filesystem with read and write permissions.
hard: Forces processes to wait and retry indefinitely if the server drops, ensuring data integrity.
_netdev: Instructs the OS to delay mounting until the network infrastructure is fully online.
nofail: Allows the system to finish booting cleanly even if the NFS server is unreachable.
x-systemd.automount: Delays the physical connection until a user or application first accesses the local directory

 

3. 디스코드 봇 생성 후 서버 초대

이제 디스코드 봇을 달 차례이다. 그전에 앞서 디스코드 서버에서 봇을 만들고, 토큰을 가져오고, 서버에 초대해줘야 한다.

디스코드 개발자 포털 > 어플리케이션 항목으로 들어가서 새 봇을 만든다. 그리고 왼쪽 메뉴를 보면 여러 항목들이 보이는 것을 알 수 있다.

여기서 해야할 것은 아래와 같다.

  • 봇: 토큰 발급받기 (이후 어플리케이션에서 사용), Intent 들 권한 3가지 전부 뚫어주기
  • OAuth2: 권한 설정 후 초대 URL 받기

설정에 정답은 없으나, 이 정도 권한은 있어야 봇 구실 하겠다 싶어서 이 정도 줬다.

이제 URL을 사용하여 봇을 서버에 초대시키자. 그러면 이걸로 디스코드에서 할 일은 대강 끝이 났다.

4. 디스코드 봇 설정 후 기동

이제 가져온 토큰을 가지고 discord 봇을 만들어 로그인시키자. 여기에서는 내가 직접 만든 코드를 활용하기로 한다.

https://github.com/kuna/discord_nasbot

 

GitHub - kuna/discord_nasbot: A personal discord bot

A personal discord bot. Contribute to kuna/discord_nasbot development by creating an account on GitHub.

github.com

해당 코드에는 proxy 제공, 그리고 HEADER 변조(웹 브라우저) + DoH(DNS over Https) + proxy 로 데이터를 받아오는 웹 크롤러 기능까지 제공하고 있다. 그래서 제공되는 함수를 그대로 사용하면 보통 웹 크롤링에 큰 문제가 없다.

아무튼 기동을 해보자. 여기에서 .env.template 파일을 .env 로 복사 후, DISCORD_API_TOKEN 항목에 아까 -3- 에서 받아온 디스코드 봇 토큰 값을 집어넣고, make run 으로 기동하면 된다. 아래와 같이 기초 명령어들이 작동한다면 성공이다.

해당 봇은 플러그인 시스템을 채택하고 있어서, 원하는 대로 자유롭게 자신이 원하는 플러그인을 짜서 넣을 수 있다. 또한 수동으로 호출되는 방식 이외에도 자발적으로 호출되도록 스케쥴링 하도록 할 수도 있게 되어 있다. 자세한 것은 README 참고.

이제 시스템이 올라올 때마다 해당 봇을 자동으로 기동하기 위해 아래와 같이 새로운 서비스를 만들어준다.

# /etc/systemd/system/nasbot.service

[Unit]
Description=Discord NAS Bot
After=network.target

[Service]
Type=simple
WorkingDirectory=/path/to/working/dir
ExecStart=/path/to/working/dir/.venv/bin/python main.py
Restart=on-failure

[Install]
WantedBy=multi-user.target

이제 서비스를 기동해보자.

sudo systemctl daemon-reload && sudo systemctl enable nasbot

sudo service nasbot restart
sudo service nasbot status

아래와 같이 정상작동한다면 작업 완료.

 

5. 용례: CloudFlare WARP + 만화 다운로더

나는 여기에 개인 플러그인으로 파일 자동 분류기, 만화 다운로더 정도를 추가해서 사용중이다. 매우 쾌적하다.

법적인 소지가 있을 수 있어 코드를 완전 공개하기는 어려우나, 대략 아래와 같은 작업을 수행하도록 해 놓았다.

  • warp 혹은 개인 프록시를 통해 통신 우회 (접속하려는 사이트가 SNI 검열 되었을 시)
  • !만화 0000000 으로 하면 자동으로 만화를 특정 디렉터리에 다운로드 수행
  • 작업을 완료했으면 명령어를 통해 warp/개인 프록시 종료

이 모든 작업을 커맨드로 할 수 있도록 구현해 놓았다. 직접 컴퓨터나 기기를 켜고 로컬에 저장해 놓을 필요가 없으니 정말 깔끔하고, 구현 또한 플러그인 형태로 파일 하나 만들면 끝이니 정말 손쉽다.

# an example
from pathlib import Path

from botcmd.dispatcher import DiscordCommandDispatcher

class MangaDownloaderDispatcher(DiscordCommandDispatcher):
		command = "manga"
		
		async def handler(self, ctx, *args):
				if len(args) == 0:
						await ctx.send(f"Usage: !manga [number] [archive?]")
						return
				number = args[0]
				do_archive = len(args) == 1 || args[1] == 'archive'
				full_url = f"<https://manga.us/{number}>"
				soup = await self.dep.web.read_html(full_url)
				
				title = soup.title.get_text(strip=True)
				links = [a["href"] for a in soup.select("a[href]")]
				img_links = [f"{link}.jpg" for link in links]
				
				await ctx.send(f"제목: {title} / 이미지 {len(img_links)} / 다운로드 시작!!")
				download_dir = Path(self.dep.config.DOWNLOAD_FOLDER)
				output_path = download_dir / f"[{number}] {title}.zip"
				
				if do_archive:
						await self.dep.web.archive(output_path, img_links)
				else:
						for i, img_link in img_links.enumerate():
								output_path = download_dir / f"[{number}] {title}" / f"{i}.jpg"
								await self.dep.web.download(output_path, img_link)
				await ctx.send(f"제목: {title} / 이미지 {len(img_links)} / 다운로드 완료!!")

참고로 !warp 는 기본적으로 포함된 플러그인이라 별도 구현은 필요 없다. 시스템에 warp-cli (Cloudflare Warp CLI)가 설치되어 있으면 된다 (이 페이지를 참조). !warp on/off/status 로 호출하면 된다.

사실 본래는 DoH + DPI 우회를 통해 SNI 검열을 피하는 방식으로 자체 프록시를 내장하려 했으나, 어떤 이유에서인지 해당 방식으로는 우회가 정상작동하지 않았다. 통신사에서 막은 것인지, 내 연구에 무언가 놓친 것이 있었는지는 아직 불명확하다. 이후에 기회가 된다면 고쳐보는 것으로 …