Hyperuplink: Discuss like it's 1998

Marius

Hyperuplink: 1998년처럼 토론하기

원문은 Marius님이 에 게재했습니다. 이 블로그 구독하기

Hyperuplink를 소개합니다. 모던한 HTML5/CSS3 기반에 100% JavaScript 없이 동작하는 인터넷 게시판 소프트웨어로, 단일 바이너리 형태로 제공되며, 별도의 런타임 의존성 없이 대부분의 주요 플랫폼에서 실행되고 PostgreSQL을 사용합니다.

Hyperuplink: 1998년처럼 토론하기

2400보 모뎀의 찢어지는 듯한 핸드셰이크 소리와 Turbo 모드로 웅웅거리는 컴퓨터 소리, 그리고 새벽 세 시에 창백하게 깜빡이던 CRT 모니터 사이에서, 인터넷에는 지난 20여 년간 완전히 잃어버린 무언가가 있었던 것 같습니다. 바로 영혼입니다. 실제 사람들이 모인 커뮤니티가 단 한 번의 요청으로 로딩되는 게시판 주변에 모여들었고, 평범한 하드웨어만으로도 주제 목록 하나 보여주자고 CPU 절반을 빌릴 필요 없이 온 세상을 화면으로 불러올 수 있었습니다. 그 짧고도 독특했던 역사의 순간을 직접 겪었다면, 아마 지금도 그 시절에 대한 애정을 간직하고 있을 것이고, 우연히 발견했던 인터넷의 기묘한 구석들과 그곳을 탐험하며 보낸 시간들, 그리고 어쩌면 그곳에서 사귄 친구들을 기억할 것입니다.

Hyperuplink는 그런 기억과 그에 얽힌 감정들을 병에 담아 2026년에도 통하는 무언가로 쏟아내려는 나의 시도입니다. Telnet이 필요 없이 브라우저 안에서 동작하는 모던한 인터넷 게시판으로, 서버 사이드에서 제대로 된 최신 HTML5와 CSS를 렌더링하고, 100% JavaScript 없이 구동되며, 외부 런타임도, 인터프리터도, FastCGI도, /var/www도, node_modules도 없이, 디스크에 흩뿌려진 파일 하나 없이 단일 정적 링크 바이너리로 제공됩니다. PostgreSQL 서버 하나는 물론 클러스터 전체와도 통신할 수 있고, 어떤 Redis 호환 캐시든 활용할 수 있으며, 향수를 자극하는 테마부터 모던한 테마까지 다양하게 갖추고 있습니다.

더 중요한 건, Hyperuplink는 재미있고, 스스로를 너무 심각하게 여기지 않으며, phpBB의 런타임 환경이나 Discourse의 망가진 JavaScript UI와 씨름하는 데 지친 모든 사람들을 위한 포럼 소프트웨어라는 점입니다. 그리고 네, Hyperuplink 역시 정말로 라마의 엉덩이를 후려칩니다.

===========================================================================
                     NOW DIALING ...  ::  CARRIER DETECTED
===========================================================================

     █   █ █   █ ████  █████ ████  █   █ ████  █     █████ █   █ █   █
     █   █ █   █ █   █ █     █   █ █   █ █   █ █       █   ██  █ █  █
     █████  ███  ████  ███   ████  █   █ ████  █       █   █ █ █ ███
     █   █   █   █     █     █  █  █   █ █     █       █   █  ██ █  █
     █   █   █   █     █████ █   █  ███  █     █████ █████ █   █ █   █

    ::: A SUPER HIGH SPEED INTERNET BULLETIN BOARD AS SINGLE BINARY :::

===========================================================================

그런데… 왜?

짧게 말하자면, 커뮤니티 채널에 상주해 있었거나 이미 이전 상태 업데이트에서 뒷이야기를 읽은 분이라면 아시겠지만, 당시 나는 내가 만들고 있는 도구와 프로그램, 서비스를 사용하는 사람들을 위한 커뮤니티 토론 포럼을 원했는데, 정작 쓰고 싶은 소프트웨어를 하나도 찾지 못했다는 겁니다.

내가 찾던 건 옛날의 좋았던 시절 BBS 시스템을 닮았으면서도 사람들이 최신 브라우저에서 편하게 쓸 수 있는 인터넷 포럼이었습니다. 그리고 이런 것들도 원했습니다…

  • 기존 데이터베이스 테이블을 이용해 사용자 인증을 할 수 있고, 그리고/또는…
  • 간단한 가입을 지원하며, 가급적이면 이메일 주소 대신 XMPP JID
  • 이메일, 그리고 가급적 XMPP를 통한 알림과 답글을 지원할 것
  • 가볍고 산더미 같은 런타임 의존성을 끌고 다니지 않을 것
  • 사용자가 JavaScript를 활성화할 필요가 없을 것
  • 아마 절대 쓰지 않을 관리 기능들로 나를 파묻지 않을 것
  • 테마 적용이 비교적 쉽고, 더 중요하게는 장기적으로 유지보수가 쉬울 것

phpBB는 가장 먼저 떠오르는 선택지였습니다. 수십 년간 존재해 왔고, DiscourseLemmy와 달리 방문자에게 JavaScript를 강요하지 않기 때문입니다. 하지만 phpBB는 기능이 너무 많은 괴물이라 설치하고 설정하는 데 시간이 오래 걸리고, 확장 기능과 런타임 의존성까지 고려하면 솔직히 내가 감당할 시간도 없는 주기적인 유지보수 의식을 요구합니다. 반면 DiscourseLemmy는 JavaScript 없이는 아예 동작하지 않기 때문에 애초에 고려 대상조차 아니었습니다. 내가 살펴본 다른 것들은 필요한 기능을 빠뜨리고 있거나, 비슷한 런타임 골칫거리를 안고 있거나, 필요한 몇 가지 기능을 넣으려면 포크를 떠서 영원히 유지보수해야 하는 것들이었습니다. 그래서 나는 합리적이고 정신적으로 건강한 선택을 했습니다. 작년 말에 직접 게시판 소프트웨어를 만들기 시작한 거죠.

기술적인 잡담

자, Go로 가보자!

한 줄의 코드도 쓰기 전에 앉아서 늘 거론되는 후보들을 저울질했습니다. PHPLaravel, PythonDjango, ElixirPhoenix, GoFiber, 그리고 ZigJetzig였죠. 서버 사이드 TypeScript는 단 1초도 고려하지 않았습니다. Node.jsNPM 생태계는 노골적인 멀웨어로 가득한 쓰레기장이라, Hyperuplink처럼 일부러 터무니없게 만든 것에도 쓰고 싶지 않았기 때문입니다.

스크립트 기반 스택은 웹 개발을 쾌적하게 만들고 지루한 HTTP, 세션, 폼 처리를 추상화해 주어 만드는 것에만 집중할 수 있게 해줍니다. 하지만 그 모든 스택은 런타임과 유지보수 부담을 뒤에 끌고 다닙니다. Hyperuplink에 대한 나의 유일한 목표는 취미로 운영하는 관리자가 스택 전체를 돌보지 않고도 게시판을 운영할 수 있게 하는 것이었습니다. 관리 관점에서 나는 가끔 바이너리 한두 개만 업데이트하면 끝나는 방식을 원했고, 수없이 많은 의존성 중 하나의 중요한 CVE를 놓치지 않으려고 PHP 보안 공지며 메일링 리스트며 GitHub의 보안 권고며 NVD까지 구독해야 하는 상황은 피하고 싶었습니다.

Go는 개발 속도를 희생하는 대신 성능을 주는 C, C++, Zig 같은 저수준 컴파일 언어와, 자료구조 다루기는 즐겁지만 실행 비용이 많이 드는 PHP, Python 같은 인터프리터 언어 사이의 절묘한 지점에 있습니다. 결정적인 요인은 Go가 단일 정적 링크 바이너리로 컴파일되어 어떤 VPS에든 복사해 바로 실행할 수 있다는 점이었습니다. 유일한 단점은 Go가 정확히 “웹 네이티브” 언어는 아니며, 지루한 부분을 가속해 줄 DjangoLaravel 같은 것이 없다는 점이었기 때문에, 나는 Fiber v3 프레임워크 위에 나만의 작은 웹 애플리케이션 프레임워크를 만들어 그 위에서 진행했습니다.

내부 구조

HyperuplinkCGO를 비활성화한 채 컴파일된 단일 정적 바이너리로, Linux, macOS, FreeBSD, NetBSD, OpenBSD 및 다양한 아키텍처로 크로스 컴파일되므로 배포는 그 바이너리를 제자리에 복사하는 것 외에는 아무것도 아닙니다. PostgreSQL 네이티브이며 클러스터 친화적이고, 읽기 성능 최적화를 위해 materialised view를 사용합니다. 스키마 마이그레이션은 내장되어 있어 시작 시 자동으로 실행되므로 외부 마이그레이션 파일이 없고 업그레이드는 새 버전을 그냥 실행하는 것만큼 쉬워야 합니다.

또한 Redis 호환 서비스가 캐싱과 세션, 비동기 작업 큐를 위해 사용됩니다. 프로필 사진, 첨부 파일, 커스텀 에셋은 로컬 디스크에 업로드할 수도 있고, 어떤 S3 호환 오브젝트 스토리지(예: MinIO)에 업로드할 수도 있어 서비스를 수평으로 확장할 때 유용합니다.

Hyperuplink는 클라이언트 사이드 JavaScript를 전혀 실행하지 않습니다. 즉, 모든 페이지가 서버에서 렌더링된 HTML5와 CSS이며, 파인애플이 피자에 어울리는지에 대한 스레드로 커서가 다시 기어가는 모습을 계정 데이터를 수집하려고 로깅하는 것 따위는 전혀 없다는 뜻입니다.

말 나온 김에, 계정은 로컬 비밀번호로 가입/로그인할 수 있으며 선택적으로 TOTP 2단계 인증을 사용할 수 있지만, Hyperuplink는 다른 플랫폼에서 끌어오려는 친구들을 위해 OAuth 제공자를 통한 로그인도 지원합니다. 그리고 이메일이 너무 꼰대스럽다고 느끼는 분들을 위해, 가입과 알림은 XMPP로도 동작합니다. 권한 부여와 관련해서는 계정을 그룹에 할당하고 카테고리별 권한을 설정할 수 있어 좋은 것좋은 사람들에게만 공개되도록 할 수 있습니다.

Hyperuplink는 다양한 사전 제작 테마를 제공하며, 그중 일부는 classic-stylesheets 프로젝트 덕분에 아름다운 레트로 감성을 자랑합니다. 조금 더 모던한 외관도 제공되며, 모든 테마에서 색상 스킴을 서로 바꿔 쓸 수 있어 Gruvbox 색감을 입힌 macOS 9 게시판 같은 것도 충분히 가능합니다.

게시판은 게시글에서 Markdown을 지원하고, 프로필 사진과 첨부 파일 업로드를 제공하며, 관리자를 위한 신고 및 관리 기능을 갖추고 있고, UI는 i18n을 지원합니다.

HyperuplinkLemmyDiscourse가 제공하는 것보다 다루기 쉽다고 생각되는 REST API도 함께 제공하며, Neon Modem Overdrive에 공식 Hyperuplink 통합으로 제공되는 자체 TUI 클라이언트도 있습니다.

시작하기

Hyperuplinktty.fail에서 개발되고 GitHub에 미러링되며, 미리 빌드된 바이너리와 컨테이너 이미지는 미러 쪽에서 빌드되고 호스팅됩니다(공짜 CPU 사이클 고마워요!). 게시판을 어떤 방식으로 실행하든, PostgreSQLRedis 호환 서버에 접근할 수 있어야 하며, 업로드를 로컬 디스크가 아닌 곳에 보관하고 싶다면 선택적으로 S3 호환 스토리지도 필요합니다.

네이티브

공식 저장소에는 최대한 빨리 시작할 수 있도록 필요한 모든 문서와 설정이 들어 있지만, 기본적인 아이디어는 릴리스 페이지에서 자신의 플랫폼에 맞는 바이너리를 가져와 원하는 곳에 두고 실행하기만 하면 된다는 것입니다:

$ ./hyperuplink -c "file:///etc/hyperuplink.toml"

Docker / Podman

전체 스택을 한 번에 띄우고 싶다면 Docker와 Podman(rootless!) 설정이 통째로 마련되어 있습니다. 저장소에는 PostgreSQLValkey, 그리고 선택적인 MinIO 프로파일을 포함한 docker-compose.yml/podman-compose.yml이 들어 있습니다:

$ docker compose up -d

Quadlets

Podman 설정은 Docker 설정이 하는 모든 것을 rootless로 수행하며, podman-compose.yml과 함께 systemd를 선호한다면 Quadlet 유닛 세트도 마련되어 있습니다.

K8s

Kubernetes도 동작하며, 몇 개의 레플리카와 Secret을 통해 전달되는 설정을 갖춘 최소한의 Deployment면 거의 모든 준비가 끝납니다. 업로드에 S3를 사용할 수 있으므로 파드는 stateless 상태를 유지합니다.

Gentoo

저장소에 Ebuild가 있으니 자신의 Gentoo… 서버에서… 아마도 직접 컴파일할 수 있습니다.

NixOS / nixpkg

Nix에 필요한 것들을 모두 포함시키려고 노력했지만, 솔직히 말해 나는 Nix를 어디서도 활발하게 쓰고 있지 않기 때문에 이건 적극적으로 유지보수되는 것이라기보다는 개념 증명 정도로 생각해 주세요. Nix 부분을 적극적으로 유지보수하고 싶으시다면 연락 주시면 기쁘겠습니다.

FreeBSD / OpenBSD / OpenRC / …

저장소에는 FreeBSD, OpenBSD, OpenRC용 init 스크립트와 심지어 systemd용 Service 정의도 들어 있습니다.

Building from sauce

Hyperuplink를 직접 빌드하고 싶다면, Go가 필요하며, 방법은 마찬가지로 간단합니다:

$ git clone https://tty.fail/mrus/hyperuplink.git
$ cd hyperuplink
$ make build

자체 완결형 바이너리는 ./build/hyperuplink에 생성되며, 원하는 곳 어디로든 옮길 준비가 됩니다.

참고: 알겠습니다, 알겠어요, 거짓말했습니다, 들켰네요. 런타임 의존성 없음은 100% 정확하지 않았고, 아마 지금 사용자의 프로필 사진 업로드를 거부하는 포럼을 보고 계실 겁니다. 이유는 Hyperuplink하나의 런타임 의존성이 있기 때문인데, 바로 ImageMagickconvert 명령어입니다. 프로필 사진이 동작하려면 서비스가 $PATH에서 그 바이너리를 찾을 수 있어야 합니다.

왜 그런지에 대해서는 이야기가 길지만, 요약하자면 이미지 처리는 어렵고, 가령 WebP 압축 알고리즘을 네이티브 Go 구현으로 새로 만들면서 바퀴를 재발명하고 싶어 하는 사람은 나를 포함해 그리 많지 않다는 겁니다. Go의 쉬운 크로스 컴파일을 유지하기 위해 CGO를 명시적으로 쓰고 싶지 않았기 때문에, convert 바이너리를 호출하는 것이 가장 합리적인 방법이라고 결정했습니다. 어차피 웹 관련 무언가를 호스팅해 본 적이 있다면 시스템에 ImageMagick이 있을 가능성이 높으니까요.

리버스 프록시

프로세스를 어떤 방식으로 실행하든 TLS를 종료하는 리버스 프록시 뒤에 두세요. Mode = "production"에서는 세션 쿠키가 HTTPS 전용이며, 포럼을 development 모드로 실행하고 싶지는 않을 테니까요. 또한 서비스를 nadzor하고 싶다면, 저장소에 앞서 언급한 systemd용, Gentoo와 Alpine용 OpenRC용, 그리고 FreeBSD와 OpenBSD용 rc.d 서비스 파일이 준비되어 있습니다.

EOF

HyperuplinkSEGV 라이선스 하에 오픈 소스로 제공되며, 코드는 tty.fail에서 확인할 수 있고 미러와 미리 빌드된 바이너리는 GitHub에 있으며, 그 밖에 읽고 싶은 모든 내용은 hyperup.link에서, 또는 Help -> Manual에서 찾을 수 있는 내장 매뉴얼 안에서 확인할 수 있습니다.

이 모든 것이 마음에 든다면 채팅방에 와서 인사하고, 게시판을 구축한 뒤 자랑도 해보세요. 그리고 개발이나 테스트에 힘을 보태고 싶다면 연락해 주세요. 최고의 커뮤니티는 언제나 진짜 사람들이 진짜 노력을 기울이는 곳이었으니까요.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.

댓글