4P by GN⁺ | ★ favorite | 댓글 1개
  • 웹 글을 터미널에서 바로 읽고 싶은 사용자를 위해 James' Coffee Blog는 블로그 글을 Linux 매뉴얼 페이지 형식으로도 제공함
  • 같은 URL이라도 클라이언트가 Accept: text/roff를 보내면 HTML 대신 roff 문서를 받도록 HTTP 콘텐츠 협상을 사용함
  • 각 글의 .man 파일은 TITLE, AUTHOR, PUBLISHED, POST, URL 섹션을 가진 템플릿으로 생성됨
  • 본문에는 Markdown 원문을 넣어 HTML보다 읽기 쉽게 만들었지만, 매뉴얼 페이지에서 간격이 항상 깔끔하게 맞지는 않음
  • NGINX가 text/roff 요청을 감지해 URL을 .man 파일로 재작성하므로, curl로 저장한 뒤 man./post.page처럼 열 수 있음

블로그 글을 man으로 읽기

  • Linux의 매뉴얼 페이지는 명령 사용법을 터미널에서 확인하는 기본 방식이며, 보통 man <command>로 열 수 있음
  • 예를 들어 tac 명령의 매뉴얼은 다음처럼 확인함
man tac
  • James' Coffee Blog는 웹 블로그 글도 같은 방식으로 읽을 수 있도록, 글 URL에서 roff 버전을 내려받아 man으로 여는 흐름을 구성함
  • 실제 요청 예시는 다음과 같음
curl -sL -H "Accept: text/roff" https://jamesg.blog/2024/02/28/programming-projects/ > post.page && man ./post.page

HTTP 콘텐츠 협상으로 형식 선택

  • 구현의 중심은 클라이언트가 원하는 응답 형식을 서버에 알리는 HTTP 콘텐츠 협상
  • Accept 헤더는 원하는 콘텐츠 타입을 전달하는 데 쓰임
    • 예를 들어 Accept: image/png는 가능하면 PNG 파일을 보내 달라는 의미임
    • 여러 콘텐츠 타입과 우선순위를 지정할 수도 있지만, 여기서는 특정 형식 요청만 사용함
  • 블로그 글을 매뉴얼 페이지 형식으로 받고 싶을 때는 Accept: text/roff 헤더를 보냄
  • 서버는 이 헤더를 보고 HTML 대신 man에서 열 수 있는 text/roff 응답을 반환함

.man 파일 생성 방식

  • Linux 매뉴얼 페이지는 roff 문법으로 작성됨
  • 사이트는 각 블로그 글마다 man 페이지 버전을 생성하도록 수정됨
  • 사용한 템플릿 구조는 다음과 같음
.TH jamesg.blog 1 "" "jamesg.blog"
.SH TITLE
...
.SH AUTHOR
James' Coffee Blog (https://jamesg.blog)
.SH PUBLISHED
...
.SH POST
...
.SH URL
...
  • 템플릿은 도메인명을 헤더로 두고 다섯 개 섹션을 만듦
    • TITLE
    • AUTHOR
    • PUBLISHED
    • POST
    • URL
  • 본문에는 Markdown 원문을 사용함
    • 매뉴얼 페이지에서 간격이 항상 잘 맞지는 않음
    • 그래도 HTML보다 읽기 쉽고, 일반 텍스트보다 제목과 문단 구분의 정보 손실이 적었음

curl로 받고 man으로 열기

  • 블로그 글의 roff 버전은 다음 명령으로 요청할 수 있음
curl -sL -H "Accept: text/roff" https://jamesg.blog/2024/02/28/programming-projects/ > post.page
  • 저장한 결과는 로컬 매뉴얼 페이지처럼 열 수 있음
man ./post.page
  • 일반 브라우저가 같은 글 URL을 요청하면 HTML 버전을 받음
  • 반면 위 curl 명령은 같은 URL에 대해 text/roff 버전을 명시적으로 요청함

NGINX에서 .man 파일로 재작성

  • 서버는 NGINX 설정 몇 줄로 text/roff 요청을 별도로 처리함
  • /etc/nginx/nginx.conf에는 특정 콘텐츠 타입이 감지되면 플래그를 세우는 변수를 선언함
map $uri $redirect_suffix {
~^/(.*)/$ $1;
default "";
}
map $http_accept $redirect_location {
default "";
"~^text/roff" 1;
}
  • 사이트 설정 파일인 /etc/nginx/sites-enabled 아래에는 roff 페이지 요청을 처리하는 규칙을 추가함
server {
...
location / {
if ($redirect_location = 1) {
rewrite ^/(.*)/$ /$1.man last;
}
...
}
}
  • 이 설정은 Accept: text/roff 헤더가 있을 때 URL의 끝 슬래시를 제거하고 .man을 붙임
  • 결과적으로 NGINX는 각 글의 index.html 대신 대응되는 .man 파일을 읽음
  • 같은 블로그 글을 웹 브라우저에서는 HTML로, 터미널에서는 Linux 매뉴얼 페이지로 읽을 수 있는 구성이 됨

댓글과 토론

Hacker News 의견들
  • 블로그 구독 방식으로 deb 저장소를 제공하면 멋질 듯함
    apt update로 모든 글을 받아오고, man your-blog로 최신 글과 전체 글 색인 링크를 볼 수 있게 하는 식

    • 아이디어 자체는 훌륭하지만, 널리 퍼지면 이 방식에 내재된 악성코드 유포 기회도 꽤 뻔해 보임
      구독하기엔 겁날 것 같음
    • 선례가 있긴 함. Debian은 예전에 지금은 사라진 Linux Gazette 접근을 제공했고, 지금도 패키지 문서, 매뉴얼 페이지, info 페이지, RFC, Linux HOWTO 등 여러 정보성 패키지를 제공함
      이들은 dwww 패키지로 로컬에서 볼 수 있음: “Read all on-line documentation with a WWW browser”
      https://packages.debian.org/bookworm/dwww
      Joerg Jaspert가 예전 Linux Gazette 패키지 관리자였음: https://people.debian.org/~joerg/ (2002)
      운영체제에 정보 전달과 문서를 통합한 사례 중 지금까지 본 것 중 손꼽히게 좋았고, 특히 전통적인 터미널 기반 인터페이스보다 man/info 문서를 더 유용하게 만들어 줌
      Debian 관련 블로그인 Debian Planet도 있지만, Debian 자체 패키지로 제공된 적은 없었던 듯함
      솔직히 블로그 구독에는 RSS가 더 나은 선택일 가능성이 큼
    • 지금 작업 중임
      https://github.com/capjamesg/jamesg.blog.deb에 아래 명령으로 man 페이지만 들어 있는 deb 파일을 만들 수 있는 내용이 있음
      git clone [https://github.com/capjamesg/jamesg.blog.deb](<https://github.com/capjamesg/jamesg.blog.deb>;)
      cd jamesg.blog.deb
      dpkg-deb --build --root-owner-group jamesg.blog
      sudo dpkg -i jamesg.blog.deb
      그러면 Processing triggers for man-db (2.9.1-1) ... 같은 출력이 보일 텐데, 이는 man jamesg.blog용 매뉴얼 페이지가 사용 가능하다는 뜻임
      지금은 자리표시자만 있고, 아마 내일 마무리할 듯함
      곧 블로그 글이 될 수도 있음
  • 포크하거나 중간 파일을 쓸 필요 없이 바로 man으로 파이프할 수 있음
    curl -sL -H "Accept: text/roff" [https://jamesg.blog/2024/02/28/programming-projects/](<https://jamesg.blog/2024/02/28/programming-projects/>;) | man -l -

    • 그러지 않는 게 좋음. 2시간 전에 yrro도 비슷한 걸 올렸고, 이제 또 {curl,wget}를 명령으로 파이프하는 논쟁이 시작됨
      친구라면 친구가 스트림을 명령에 바로 파이프하게 두지 않음
      https://news.ycombinator.com/item?id=39554044
  • 참고로 curl -sL -H "Accept: text/roff" [https://jamesg.blog/2024/02/28/programming-projects/](<https://jamesg.blog/2024/02/28/programming-projects/>;) | man -l /dev/stdin이 내 환경에서는 동작함
    roff 파일을 로컬에 저장할 필요가 없음

    • 원글 작성자가 일부러 이렇게 안 한 것 같음. 인터넷에서 받은 명령이나 내용을 bash 같은 데 바로 파이프하는 건 보통 나쁜 관행으로 여겨짐
      개인적으로는 괜찮다고 봄. 보안상 의미를 아는 사람은 이런 변환 방법도 거의 확실히 알고 있으니 굳이 알려줄 필요가 없음
      하지만 초보자에게 알려주기엔 좋지 않음. 언젠가 당하게 될 수 있음. 실력이 늘면 자연스럽게 이런 기능을 알게 될 테고, 그때쯤엔 함의도 배웠기를 바람
      내가 쓴 글은 아님: https://www.seancassidy.me/dont-pipe-to-your-shell.html
    • 안타깝게도 그 명령은 macOS에서는 동작하지 않음: /usr/bin/man: illegal option -- l
      Mac에서 파이프를 쓰는 한 줄 명령을 만들려고 해봤지만 계속 오류가 났음
      macOS의 man 구현에는 -l 플래그가 없음. 매뉴얼 페이지를 확인했음
    • bash를 쓴다면 파이프 대신 프로세스 치환으로 몇 글자 줄일 수 있음
      man -l <(curl -sL -H "Accept: text/roff" https://jamesg.blog/2024/02/28/programming-projects/)
  • 터미널에서 재미있는 일을 하는 URL 얘기라면, 예전에 textfiles.com에서 본 게 있음
    VT100 터미널 코드로 짧은 애니메이션 영화를 보여주는 형태이고, 전부 하나의 URI에서 제공됨
    현대 시스템에서는 속도 제한을 걸어 볼 수 있음
    curl --limit-rate 1000 [http://textfiles.com/sf/STARTREK/trek.vt](<http://textfiles.com/sf/STARTREK/trek.vt>;) && reset
    reset은 터미널이 망가질 수 있어서 넣은 것임
    다른 터미널 기반 URI로는 curl cheat.sh/tar/ 뒤 프로그램 사용 예시를 가져오고, curl wttr.in/berlin은 터미널 서식이 적용된 날씨 정보를 가져옴

    • telnet으로 직접 ASCII 비디오를 만들고 싶다면 몇 년 전에 Go로 만든 게 있음: https://github.com/bfontaine/RickASCIIRoll
      실제로는 꽤 단순하고, 가장 어려운 부분은 프레임 생성임
      이건 ffmpeg+img2txt.py로 할 수 있음: https://github.com/bfontaine/RickASCIIRoll/tree/master/movie...
    • 몇 년 전에 모뎀 속도 에뮬레이션이 들어간 ANSI 아트 뷰어를 만들었음
      https://16colo.rs/의 오래된 미러가 있어서, 지금까지 공개된 대부분의 ANSI 아트를 볼 수 있음
      예: curl ansi.hrtk.in/ungenannt_1453.ans
    • 정말 멋진데, 터미널도 완전히 망가뜨렸음. 재미있었음
    • telnet으로 보는 Star Wars도 있음
      https://itsfoss.com/star-wars-linux/
    • tritty를 쓰면 1200/9600 BPS 전송 속도를 흉내낼 수 있음
  • 이제 필요한 건 Markdown을 roff로 바꾸는 변환기뿐인데, 찾아보니 이미 있음
    https://github.com/postmodern/kramdown-man
    https://rtomayko.github.io/ronn/ronn.1.html
    https://kristaps.bsd.lv/lowdown/

  • Emacs 패키지 중 Abelson과 Sussman의 SICP를 Info 디렉터리에 설치해 주는 것이 있음
    M-x package-install sicp RET만 입력하면 됨
    이걸 보고, 수정한 피드 리더로 블로그 아카이브 책장 전체를 설치할 수도 있겠다는 생각이 들었음
    Emacs에서 Info를 읽으면 북마크도 쓸 수 있음

    • chicken-scheme도 설치하면 됨. 그다음 root로 실행함
      chicken-install srfi-203
      chicken-install srtfi216
      SICP용 ~/.csirc는 다음과 같음
      (import scheme)
      (import (srfi 203))
      (import (srfi 216))
      (define (inc x) (+ x 1))
      (define (dec x) (- x 1))
      그다음 평소처럼 user geiser와 chicken용 geiser를 쓰면 됨
    • 참고로 SICP는 Abelson과 Sussman의 책임
  • 인터넷에서 찾아보면 답을 알 수도 있겠지만 HN에 묻고 싶음
    고등학교 때 HP-UX에서 누군가가 밑줄 친 단어, 즉 섹션 참조로 어떤 키 조합을 눌러 점프하는 걸 보여준 기억이 있는데 도저히 어떤 키였는지 모르겠음
    man(1)man(7)도 확인했지만 못 찾았음. 가짜 기억일 수도 있음

    • 그게 man이었다면, man ohman은 본질적으로 nroff -man /usr/share/man/man1/ohman.1 | $PAGER라는 점을 생각해야 함
      즉 man이나 nroff와 상호작용하는 게 아니라 페이저와 상호작용하는 것임
      요즘은 less가 가장 흔하고, more도 사실상 less일 가능성이 크지만, 예전에는 다른 것도 있었고 HPUX는 pg 같은 걸 썼을 수 있음
      pg는 AT&T 계열, more는 BSD 계열, less는 GNU 계열이었음
      셋 모두 /로 정규식 검색을 시작하므로 밑줄 여부와 상관없이 찾을 수 있음
      less는 태그 파일도 지원해서 t로 다음 태그로 점프할 수 있음
    • 별도 man 뷰어 기능은 잘 모르겠지만, CDE 도움말 뷰어dthelpview를 떠올린 것일 수도 있음. 이게 man 페이지를 표시했을 수 있음
    • 이건 info 명령으로 여는 texinfo처럼 들림
      아이러니하게도 원래 groff 문서 상당수가 texinfo로 작성되어 있음: https://lists.gnu.org/archive/html/groff/2005-10/msg00107.ht...
  • 왜 이 사소한 부분이 내 꼬투리 잡기 본능을 건드렸는지 모르겠음. 인터넷에서 누군가가 살짝 틀렸기 때문일지도 모름
    처음부터 불필요하게 Linux 중심적이었거나, 뭔가 다른 걸 기대했는데 결국 NGINX의 콘텐츠 협상 짧은 데모였기 때문일 수도 있음
    아무튼 굳이 말하고 싶은 쓸데없는 점들이 있음
    엄밀히 말해 roff를 반환하는 게 아님. .TH 같은 것은 roff 자체가 아니라 man 페이지 작성용 매크로 패키지의 일부임
    Markdown-to-roff 변환이 없어서 실망했음. 그게 이 글의 흥미로운 부분일 줄 알았고, 적어도 기존 도구 하나는 쓸 수 있었음
    비슷하게, 이 때문에 텍스트 서식도 사실 제대로 맞지 않음. roff 입력은 문장 끝의 .과 다른 용도의 .을 구분하기 위해 한 문장에 한 줄을 의도함
    또한 .으로 시작하는 모든 줄이 명령으로 해석되어 문제를 일으킬 수 있음
    아니면 그냥 내가 심술궂은 노인일 수도 있음

    • 이걸 공유해줘서 고마움. roff와 man의 관계가 정확히 어떤 구조인지 몰랐고, 이 글을 여러 번 고치면서 맞추려고 했음
      groff, nroff 같은 다른 도구들이 있어서 더 헷갈렸음
      “roff/man page/nroff/다른 변형이 무엇이고 어떻게 쓰는지”만 설명하는 글도 충분히 하나의 블로그 글이 될 만함
      짧고 명확한 설명이 있었다면 나도 좋았을 것이고, 다른 사람들에게도 도움이 될 듯함
      Markdown-to-roff는 v2로 생각했음. 파서를 구현하려고 고민하기 시작했을 때 누군가 https://github.com/sunaku/md2man을 알려줬고, 이 문제가 해결되는 것처럼 보임
      GitHub Pages 위에서 돌아가는 내 Python 사이트에 이걸 어떻게 통합할지 알아봐야 해서 약간 손봐야 함
    • Markdown-to-roff 변환이 없었던 건 나도 꽤 놀랐음
      Pandoc은 Markdown을 man-page roff로 아주 쉽게 변환할 수 있음
      그걸 주어진 템플릿에 넣으면 실제 man 페이지처럼 더 잘 보일 것임
  • 올바른 미디어 타입은 RFC 4263 기준으로 text/troff임: https://www.rfc-editor.org/rfc/rfc4263.html

  • 멋진 아이디어임. 이제 “내 블로그 글을 플레이 가능한 DOOM WAD로 제공하기”가 나오기까지 타이머를 재면 됨

    • AI가 실제로 도와줄 수 있는 몇 안 되는 멋진 일 목록에 추가하면 됨