3P by GN⁺ | ★ favorite | 댓글 1개
  • 개인 웹사이트를 단일 바이너리로 만들면, 한 사람이 전체 구조를 이해하고 직접 유지보수할 수 있어 기존 SSG·호스팅 조합의 의존성 부담이 줄어듦
  • Hugo와 GitHub Pages 같은 조합은 서비스 지속성, 프로젝트 유지, 플러그인 생태계, 배포 방식, Go 등 여러 외부 조건을 전제로 함
  • 수동 HTML 편집은 단순하고 안정적이지만, 공통 기능을 반복해서 다루기 어렵고 작업 흐름이 피로해져 더 즐겁게 조정 가능한 방식이 필요했음
  • Go 기반 동적 사이트는 IP 표시나 RSS 피드 갱신처럼 정적 사이트에서 번거로운 작업을 서버 측 코드로 처리하고, 브라우저가 아닌 도구에도 완성된 HTML을 반환함
  • 개인 웹사이트는 작게 시작해 표준 라이브러리와 단순한 도구로 키우는 편이 오래가며, 큰 프레임워크로 시작하면 개인 시간 안에서 따라가기 어려움

기존 웹사이트 도구가 맞지 않았던 이유

  • Ghost, Hugo, Jekyll, sr.ht와 tarball, 수동 HTML 편집까지 여러 방식을 시도했지만, 개인 기준에 맞지 않았고 흥미도 주지 못했음
  • 가장 중요한 기준은 한 사람이 처음부터 끝까지 이해하고 쉽게 유지보수할 수 있는 시스템이었음
    • 읽기 쉬운 코드
    • 빠른 피드백 루프
    • 재미와 영리함
    • 고장 났을 때 직접 파고들 수 있는 구조
  • Hugo 같은 정적 사이트 생성기(SSG)는 템플릿과 Markdown을 입력으로 받아 HTML을 만들고, 보통 호스팅 플랫폼과 함께 사용됨
  • Hugo와 GitHub Pages 조합에는 여러 유지보수 조건이 따라붙음
    • GitHub Pages 서비스가 계속 동작해야 함
    • GitHub Pages 제품이 계속 유지되어야 함
    • Hugo 프로젝트와 커뮤니티 문서가 유지되어야 함
    • 플러그인 생태계에 의존할 수 있음
    • Hugo 바이너리가 시간이 지나도 HTML을 거의 같은 방식으로 생성해야 함
    • GitHub Pages 배포 메커니즘이 계속 작동해야 함
    • Go가 필요함
  • 특정 Hugo 버전을 고정해 쓰는 방법도 있지만, 실제로는 버그 때문에 업그레이드가 필요해지는 일이 생김

수동 HTML 편집의 장점과 한계

  • 직접 제어하는 머신에 웹사이트를 호스팅하고, Vim으로 HTML을 손수 편집하는 방식도 선택했음
  • j3s.sh의 첫 버전은 이 방식으로 만들어졌고, 현재 레이아웃 아이디어 중 상당수가 여기서 나왔음
  • 손으로 HTML을 작성하면 고된 수작업처럼 느껴져 극단적인 미니멀리즘으로 이어짐
    • 스타일을 HTML 안에 직접 넣음
    • 사이트 전반의 공통 기능을 최소화함
    • 스타일링을 많이 피함
  • 이 방식은 단순하고 안정적이며 유지보수하기 쉬웠음
    • 작성한 HTML은 수정 없이 재사용 가능했음
    • 외부 의존성이 없었음
    • 사이트가 오래 유지될 것이라는 확신을 줬음
  • 다만 공통 기능의 템플릿화를 더 효율적으로 처리하고 싶었고, 기존 프레임워크 없이 재미있게 조정 가능한 방식이 필요했음

Go 단일 바이너리 웹사이트

  • 웹사이트 전체를 하나의 언어로 작성하면, 결과물을 단일 바이너리에 담아 배포할 수 있음
  • Go는 개인 유지보수와 단순 배포에 잘 맞는 선택지였음
    • 한 사람이 유지보수하기 쉬움
    • 의존성을 적게 쓰는 방향을 강조함
    • 언어가 비교적 단순함
    • 하위 호환성 보장이 있음
    • 정적 컴파일 바이너리를 쉽게 만들 수 있음
    • 대부분 10초 미만으로 컴파일됨
    • 표준 라이브러리가 잘 발달해 있음
  • 결과적으로 j3s.sh는 웹사이트를 서빙하는 하나의 바이너리가 됨

동적 웹사이트가 주는 단순함

  • 경우에 따라 정적 생성 웹사이트보다 동적 생성 웹사이트가 더 단순할 수 있음
    • 콘텐츠 생성을 위한 우회 방법을 고민하지 않아도 됨
    • 필요한 내용을 즉석에서 생성할 수 있음
  • 방문자의 IP 주소를 출력하는 기능은 Go의 http.HandlerFunc로 간단히 구현됨
    • X-Forwarded-For 헤더를 읽음
    • /ip 경로에 핸들러를 연결함
    • 필요한 곳에서 같은 함수를 호출할 수 있음
  • 정적 사이트에서 같은 기능을 구현하려면 JavaScript 같은 외부 의존성이 필요할 수 있고, JavaScript가 없는 경우도 고려해야 함
    • 일부 독자는 JavaScript 실행을 거부함
  • 서버가 이미 IP 주소가 포함된 HTML을 반환하면, 클라이언트가 JavaScript를 이해하지 못해도 올바른 응답을 받을 수 있음
    • 브라우저가 아닌 도구에도 같은 방식이 통함
  • 홈페이지의 RSS 피드는 웹사이트 시작 시점에 생성됨
    • 친구들의 최신 글을 새로 가져올 수 있음
    • 직접 만든 RSS 피드 fetcher가 오랫동안 유지보수 없이 동작할 것으로 봄
    • 내부 동작을 정확히 이해하고 있음

배포와 시작 방법

  • 배포는 짧은 쉘 스크립트로 처리됨
    • Git 저장소를 가져옴
    • 로컬 브랜치가 뒤처져 있으면 git merge origin/main을 실행함
    • go build로 빌드함
    • 바이너리를 /usr/local/bin/j3s.sh로 옮김
    • service j3s.sh restart로 서비스를 재시작함
  • 이 스크립트는 cronjob으로 매분 실행되어, 저장소가 업데이트되면 사이트를 다시 빌드함
    • 로컬에서 테스트하고 업로드하면 1분 안에 공개 사이트에 반영됨
    • 쉘을 조금 알면 10초 안에 이해할 수 있을 정도로 단순함
  • 개인 웹사이트는 요즘 웹에서 독특한 느낌을 주며, 운영자가 중요하게 여기는 것을 깊게 반영할 수 있음
  • 정적 사이트가 맞는 사람은 그대로 쓰면 되지만, 비슷한 가치관을 가진 사람에게는 동적 웹사이트 시도가 잘 맞을 수 있음
    • 작게 시작할 수 있음
    • 재미있는 아이디어를 반복적으로 추가할 수 있음
    • 자기 사이트이므로 하드코딩해도 됨
    • Rust, Perl, Python 등 좋아하는 언어로 만들 수 있음
  • 시작은 작아야 함
    • 먼저 index.html을 만들고, 선택한 언어로 이를 서빙하는 데 집중함
    • RSS나 Markdown은 나중에 필요해질 때 추가함
    • 가능하면 표준 라이브러리처럼 가장 단순하고 유지보수 가능한 도구를 씀
    • Ruby에서는 Rails 대신 Sinatra, Python에서는 Flask 대신 Gin 같은 더 작은 선택지를 고려할 수 있음
    • 라이브러리 0개로 직접 구현할 수 있는지도 먼저 시도해볼 수 있음
  • 큰 프레임워크나 계속 breaking change가 나는 프로젝트로 시작하면 개인 시간 안에서 따라가기 어렵고 빠르게 지칠 수 있음
  • 웹에는 더 많은 이상함, 흥분, 개성이 필요하며, 동적 웹사이트를 만들어보는 시도가 권장됨

댓글과 토론

Hacker News 의견들
  • 모바일에서 웹사이트 너비가 제대로 계산되지 않아 글을 읽으려면 좌우로 스크롤해야 함
    그걸 하던 바로 직전에 “나는 매우 높고 특이한 기준을 갖고 있다”는 문장을 읽어서 솔직히 좀 웃겼음 :)

    • HTML로 섹션, 문단 같은 구조를 잡은 글이 아니라, 그냥 일반 텍스트 파일을 공백 처리 pre로 렌더링해서 거대한 한 문단처럼 보이는 것뿐임 ¯_(ツ)_/¯
    • Safari 읽기 도구에서도 서식이 0이라서 그것도 꽤 인상적이었음
      이상한 서식은 자주 봤지만, 아예 서식이 없는 건 처음임
    • Firefox Mobile의 읽기 모드로는 해결됐으니 실제로는 버튼 하나 누르는 정도의 불편함임
      그래도 첫인상은 좋지 않음
    • 작성자의 논리와 플랫폼에 맞춰 만들지 않은 점이 서로 충돌함
      이해가 안 됨
    • 나도 같은 생각이었음
      본인이 자부심을 갖는 건 좋지만, 난 그냥 읽지 않기로 함
  • 단순하게 유지한다”는 게 대체 무엇인지, “이해 가능하다”는 게 무엇인지가 핵심임
    이런 글들은 흥미로운 논쟁은 건너뛰고 자기 접근법이 단순하며 목표가 단순성이라고 가정해버림
    추상화, 예컨대 라이브러리나 프레임워크, 서비스나 API를 선택할 수 있을 때 쓰는 게 더 단순한가, 안 쓰는 게 더 단순한가?
    안 쓰고 직접 만들면 시스템 전체가 어떻게 동작하는지 더 잘 알게 되니 어떤 의미에서는 더 단순함
    하지만 복잡성은 여전히 생기고, 경우에 따라 추상화 계층도 직접 만들어야 함
    반대로 쓰면 복잡성을 숨기므로 정상 경로에서는 시스템이 더 단순하다고 볼 수 있지만, 그 복잡성은 여전히 존재하고 숨겨져 있어서 이해하지 못함
    두 방식 모두 특정 관점에서는 명백히 단순해 보이지만, 그 명백함을 넘어서야 트레이드오프가 보임

    • 애플파이를 만들고 싶다고 해보자
      보통은 재료와 도구를 사고, 이미 있는 오븐에서 굽게 됨
      하지만 처음부터 애플파이를 만들려면 먼저 우주를 만들어야 하고, 아마 애플파이를 지원하는 가장 단순한 우주를 만들게 될 것임
      전체 스택은 더 단순할 수 있지만, 마지막 단계인 실제 파이 만들기는 더 복잡해질 수 있고 맛도 최고가 아닐 수 있음
      대신 제1원리부터 완전히 이해하는 파이를 얻게 됨
    • 여기서 말하는 건 “단순함(simple)”과 “쉬움(easy)”의 차이에 가까워 보임
      Rich Hickey의 이 발표를 좋아할 수도 있음: https://paulrcook.com/blog/simple-made-easy
      무언가를 더 쉽게 만들거나 배포하려고 기능을 추가할 수는 있지만, 그 추가 때문에 전체는 더 복잡해지고 이해하거나 문제를 추적하기는 어려워짐
    • 이 글과 관련해서는 이 댓글이 잘 이해되지 않음
      작성자는 마지막에 아주 명확하게 각자 자기 방식대로 하라고 말함
      게다가 본인이 이해하기 어렵거나 부담스러운 시스템을 쓰는 데 만족하지 않는다고도 함
      첫 문단 이후도 읽어보는 게 좋겠음
  • 아주 멋짐!
    “여러분도 이렇게 해야 합니다!”에는 절대 동의하지 않음 :)
    유지보수성은 내 핵심 가치 중 하나라서, 당신처럼 자전거 정비도 직접 함
    하지만 내게는 이 정도로 1순위가 아니어서 이런 선택이 말이 되지는 않음
    Framework 노트북이 우리 차이를 잘 보여주는 지점임
    당신은 DIY 쪽에서 계속 9점을 뽑아내지만, 나는 언제든 Linux와 Neovim을 급히 돌릴 수 있는 10년 된 ThinkPad 예비기를 갖고 있고, 그마저 망가지면 앞으로도 100달러 미만 ThinkPad가 많을 거라고 믿는 쪽으로 회복력을 확보함
    언젠가 필요해지면 Haskell 조합자로 Markdown에서 HTML로 느리고 비공식적이며 버그 많은 파서를 쓸 수 있을 만큼의 컴퓨터과학과 수학 지식은 갖고 다니지만, 그런 날이 곧 올 것 같지는 않음
    가능하다면 당신을 채용하고 싶음
    반대 방향으로 기우는 수많은 사람들에게 완벽한 균형추가 될 것임

    • 반대 방향으로 기운다는 게 어떤 걸 뜻하는지 궁금함
  • 이 글을 정말 즐겁게 읽었음
    작성자는 자신에게 솔직하고 독자에게 진지함
    변명 없이 자기 자신으로 존재하며, 자신의 가치와 맞고 즐거움을 주는 방식으로 목표를 달성하려고 함
    그 가치가 당신과 맞지 않아도 괜찮고, 오히려 아름다운 일임
    이제 당신이 탐색할 수 있는 길에 대한 단서를 얻은 셈임
    글은 친절하고 겸손하며 진정성 있고, 작성자와 글이 세상을 조금 더 나은 곳으로 만든다고 생각함

    • 진지한 건 맞지만, 자신에게 솔직하다는 건 전혀 반대에 가까움
      겸손하다는 것도 마찬가지임
      전체 글은 자기 칭찬에 가깝고, 중간중간 /r/confidentlyincorrect 같은 단편도 보임
    • 이 글은 독자를 위한 글이라기보다, 작성자가 학습이나 통제감 같은 내적 목표를 달성하기 위한 연습이자 보조 도구로 보는 편이 더 쉬움
      독자는 부수적인 대상일 뿐이라서, 독자 경험이 아무리 스파르타식이어도 핵심은 아니고 작성자의 경험이 핵심임
    • 읽었다면 내용을 좀 공유해줄 수 있음?
      모바일 플랫폼에서 온 사람들 다수가 너무 일찍 읽기를 포기한 것 같음
  • 대충 훑어봤는데, 이 사람은 거의 확실히 우리를 낚고 있는 것 같음
    웹사이트는 말 그대로 “plain HTML”을 쓴다고 하지만 실제로는 plain text
    여기엔 서식용 HTML이 전혀 없음
    제목도, 문단도, 목록도 없고, 심지어 a 태그도 없음
    조금 들어간 HTML과 CSS는 오히려 사이트를 더 나쁘게 렌더링함
    글쓴이는 트롤임

    • 그 요소는 최소 15년 전부터 폐기된 요소로 표시되어 있음
  • “나는 매우 높고 특이한 기준을 갖고 있다”면서, “이 스크립트는 cronjob으로 매분 실행되고 git 저장소가 업데이트되었으면 사이트를 다시 빌드한다”고 함
    1년에 몇 개 커밋하지 않을 저장소를 연 525,600번 가져오는 셈임
    “특이한”이라는 단어가 왜 들어갔는지는 알겠음

    • 새 커밋이 없으면 git fetch는 사실상 문자열 비교 한 번에 가깝기 때문에 [0], 말하는 것만큼 비용이 큰 작업은 아니라고 봄
      [0] https://stackoverflow.com/a/44476803
    • 이 상황에서는 “특이한”보다는 미친 짓에 가까움
      묻지 말고 알려줘야 함
    • 여기서는 내 배포가 어떻게 동작하는지에 대해 너무 많은 가정을 하고 있음!
      내 git 저장소들은 전부 웹사이트를 호스팅하는 같은 서버에 있음
      git은 원격 fetch를 하는 게 아니라 로컬 fetch를 하므로 사실상 즉시 끝남
      설령 원격 fetch였어도 괜찮았을 것임
      연 525,600번 HTTP 요청은 NetworkManager의 하트비트보다 덜 낭비적임
      Arch에서 기본값은 30초마다 하트비트 요청 1번임
      참고로 CPU 8개짜리 기본 nginx 서버는 초당 50만 건 이상의 HTTP 요청을 처리할 수 있음
  • OP에게:
    여기에는 매우 비판적인 글이 많이 보임
    정중히 말해서… 신경 쓰지 마셈
    비판이 맞을 수도 있지만, 업계는 집단적인 “해야 한다”와 “마땅히 그래야 한다”를 따라가며 빠르게 대규모 어리석음으로 향하고 있음
    그중 상당수는 현재로서는 도시전설 수준의 타당성밖에 없음
    요즘 “엔지니어링”이라고 불리는 것은 우리가 그 위에 미래를 적극적으로 만들고 있지 않았다면 웃고 넘길 만함
    내 아버지는 항공우주 엔지니어였는데, 현대 소프트웨어 개발에서 엔지니어링이라고 불리는 것을 보면 무덤에서 뒤척이실 것 같음
    댓글란이니 누구나 말할 자유는 있지만, 개인적으로는 업계에서 25년이 지나서야 그 수다스러운 목소리들을 무시하고, 드물게 가능한 기회에서 내가 말이 된다고 느끼는 걸 하게 되었음
    하물며 개인 사이트라면 더더욱 그렇지 않나?
    Apple 광고의 “미친 사람들에게”라는 그 오래된 문구도 지금은 진실에서 멀게 들림
    인터넷이 이렇게 되었고, 한때 스타트업이던 곳들이 이제 지형에서 가장 큰 기업 괴물이 되었으니 놀랄 일도 아님
    Theodore Roosevelt의 “경기장 안의 사람” 인용구가 요즘 정말 맞는 말임
    계속해라, OP, 계속해라…

    • 격려 고마움! 특히 HN에서, 특히 내 개인 웹사이트 글에 대한 비판은 가볍게 받아들이려고 함
      내 웹사이트는 완벽히 최적화된 사이트가 아니라, 나만의 신성하고 독특한 놀이터
      전부 직접 쓰고 시간이 지나며 진화함
      내 스타일이 사람들에게 거슬릴 때가 있다는 건 분명하고, 그런 비판에는 익숙함
      내가 좋아하는 형태로 콘텐츠를 구부려 넣기 위해 이상한 결정을 하기도 하지만, 바로 그 점이 내 콘텐츠를 사랑스럽게 만든다고 생각함
      바깥의 다른 것들과 같지 않기 때문임
      거친 모서리는 있겠지만, “개인적인 형식”을 탐구하면서 점점 나아질 것임 :)
      내게 웹사이트는 장기적인 예술 프로젝트와 같음
      작성자를 나타내야 하고, 내 경우 내 사이트는 당당하게 나를 나타냄
  • 당신 것이 나왔으니, 내 것도 보여주겠음
    내 웹사이트, 게임 엔진, 웹서버는 하나의 바이너리
    소스는 C 코드 1000줄이고, 바이너리 크기는 164KB임
    왜 이렇게 빌어먹게 큰지는 모르겠음
    웹서버도 직접 포함했음
    기존 서버들은 숨겨진 복잡성을 너무 많이 더함
    결과물인 독일어 인터랙티브 픽션 게임은 여기서 볼 수 있음: http://vmd34232.contaboserver.net/
    추적 없음, 광고 없음, 상업적 목적 없음

    • 좋네, 예전에 parchment.js로 inform7이 만든 z5 파일을 내 웹사이트에서 로드한 적이 있음
      실행 파일은 upx로 압축해볼 수 있음 https://upx.github.io/
    • 내 사이트가 떠오름 :)
      https://toldby.ai/
      내 건 단일 바이너리는 아니고 asp.net임
  • 글에서 제기한 모든 지점에는 전적으로 동의하지만, 이 웹사이트는 예시로는 좀 별로임
    웹사이트가 “하나의 바이너리”라고 하지만, 배포 전략은 바이너리 산출물을 실행하는 대신 컴파일을 포함
    github pages에는 의존하지 않겠다고 하면서 openlibrary.org의 외부 리소스에는 의존함
    직접 만들면 공개 표준을 활용할 수 있다고 하지만, thoughts/ 안의 .html 파일들은 거짓 확장자를 단 일반 텍스트 구조임
    사람들이 이 길을 택할 때는 보통 초기 페이지 로드에 모든 걸 밀어 넣으려 하는데, 여기에는 동적으로 제공되는 정적 파일이 여러 개 있음
    Golang 코드는 어떤 응답도 캐시하지 않고 템플릿도 메모리에 저장하지 않음
    go.mod 파일에 의존성이 여러 개 있는데, 모두 쓰이지 않는 것처럼 보임
    가장 중요한 건, 단일 바이너리 정신이 현대 인프라와 비교해 어떤 기술적 장점이 있는지 논의가 없음
    개인적으로는 좋은 아이디어지만, 이건 나쁜 예시라고 봄

    • 실제로는 단일 바이너리가 아님
      파일시스템과 함께 동작하는 단일 바이너리임
      모든 콘텐츠는 파일시스템에 있음
      “단일 바이너리”는 파일시스템에서 콘텐츠를 서빙하는 가벼운 Golang 웹서버
  • “매우 높고 특이한 기준”이라는데, 특이한 건 맞지만 결과물인 웹사이트 디자인이 너무 형편없어서 화면에 맞게 텍스트도 제대로 보여주지 못하니 기준은 오히려 매우 낮음